Compare commits

...

175 Commits

Author SHA1 Message Date
Eric Bailey a1944006ca Remove unused script 2024-12-04 12:23:50 -06:00
Eric Bailey 11df003104 Make react-remove-scroll an explicit dep 2024-12-04 12:23:50 -06:00
Eric Bailey 3fd1b4e0e4 Replace scroll-removal handling 2024-12-04 12:23:50 -06:00
Eric Bailey 41c0cf9d34 Handle shifts in layout components 2024-12-04 12:23:49 -06:00
Eric Bailey 414aa379ea Remove bg in sidebars 2024-12-04 12:23:26 -06:00
Eric Bailey 251da0be52 Add transform to sidebars 2024-12-04 12:23:26 -06:00
Eric Bailey b2143cd5b4 Reset 2024-12-04 12:23:26 -06:00
Eric Bailey 7b5a5ccbf9 Increase width 2024-12-04 12:23:26 -06:00
Eric Bailey 1a81d135de Ope 2024-12-04 12:23:26 -06:00
Eric Bailey 415e3b9501 Use stable one-edge, update logic in RightNav 2024-12-04 12:23:26 -06:00
Eric Bailey f8f6d213a3 Gift left nav more space 2024-12-04 12:23:26 -06:00
Eric Bailey fceaef6bae Make sure scrollbars don't hide 2024-12-04 12:23:26 -06:00
Eric Bailey d53b8e58ce Fix sidebar alignment 2024-12-04 12:23:26 -06:00
Samuel Newman 0bd55e2562 fix breakpoints 2024-12-04 18:17:56 +00:00
Eric Bailey 4669e66e07 Replace empty chat layout 2024-12-04 12:10:00 -06:00
Eric Bailey de940fad9b Move ChatList header outside center 2024-12-04 12:05:50 -06:00
Eric Bailey 8cf3566c7d Remove unused var 2024-12-04 11:53:31 -06:00
Eric Bailey 73acee0381 Add web borders to Chat settings 2024-12-04 11:47:32 -06:00
Eric Bailey 3711f9353d [Layout] ChatList header (#6929)
* Replace ChatList header

* update chat settings as well

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2024-12-04 11:39:59 -06:00
Eric Bailey 5db4af49d9 Replace Hashtag header (#6928) 2024-12-04 11:39:46 -06:00
Eric Bailey 16e2da3881 [Layout] Notifications Header (#6910)
* Replace notifications screen header

* fix cropped indicator

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2024-12-04 11:39:37 -06:00
Samuel Newman 05b5148d38 set max with on header and center 2024-12-04 16:47:14 +00:00
Samuel Newman bd61b94e5a sticky headers 2024-12-04 16:30:21 +00:00
Eric Bailey 8a21350472 Fix alignment 2024-12-03 13:15:37 -06:00
Eric Bailey 48623b386b Allow two lines, fix wrapping 2024-12-03 12:23:20 -06:00
Eric Bailey e2dcb53937 Limit to one line 2024-12-03 12:09:13 -06:00
Eric Bailey 8167885aa8 Trade boilerplate for ease of use 2024-12-02 18:42:52 -06:00
Eric Bailey e5470f946f Port over FF handling of scrollbar offset 2024-12-02 18:19:42 -06:00
Eric Bailey 2e32d1dad6 Clean up Slot API 2024-12-02 17:17:50 -06:00
Eric Bailey 0aa1a0ff91 Invert web border logic for easier migration 2024-12-02 17:15:57 -06:00
Eric Bailey e40044b0b1 Remove old back button 2024-12-02 17:12:08 -06:00
Eric Bailey 080a0186c7 Replace layout components in settings screens 2024-12-02 17:12:01 -06:00
Eric Bailey 29db02cab7 Add new layout components 2024-12-02 17:11:27 -06:00
Eric Bailey 69378a393c Add computed scrollbar gutter CSS vars 2024-12-02 17:11:00 -06:00
Eric Bailey 55eae34dc8 Add common gutter styles as hook 2024-12-02 17:10:03 -06:00
dan 5f4a0f2881 "Contain" images with missing dimensions instead of cropping them (#6828)
* Show unknown aspect as "contain" for autosize

* Fix a flash of wrong position when opening in lightbox

* Fix last frame flash on Android
2024-11-28 23:29:45 +00:00
dan d08ce0d473 Fix stuck lightbox (#6816)
* Fix lightbox getting stuck by fixing rAF order

If you spam opening lightbox too fast, the effect that calls rAF will clean up and set up again midflight. Unfortunately, due to rAF order being unreliable, it may fire in reverse order, causing "open, open, close" instead of "open, close, open", so it would get stuck closed. This fixes the rAF order.

* Don't allow opening another lightbox while it's open
2024-11-28 16:55:05 +00:00
Samuel Newman 0aee6390fa ensure black root view on iOS (#6813) 2024-11-28 15:41:33 +00:00
Foster 84796c1bcf fix iOS language picker style in dark mode (#6804)
Co-authored-by: skipness <ckhf@proton.me>
2024-11-28 14:31:10 +00:00
dan 6a2067a761 Revert "Revert "bump version to 1.96 (#6781)" (#6790)" (#6794)
This reverts commit c9d86de152.
2024-11-27 22:05:31 +00:00
dan c9d86de152 Revert "bump version to 1.96 (#6781)" (#6790)
This reverts commit b2c232e9fd.
2024-11-27 19:21:08 +00:00
Samuel Newman 29b3b3db30 YT embed tweak (#6789)
* tweak css

* fix typo

* More tweak

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-27 19:19:36 +00:00
dan 47cbdfecb3 Fix web build by removing app icon settings (#6784) 2024-11-27 15:34:12 +00:00
dan 763a5d5ab5 Fix gap between Write Your Reply and bottom bar on web (#6783)
* Fix gap between Write Your Reply and bottom bar on web

* rm log
2024-11-27 14:54:09 +00:00
Samuel Newman 6a29296ae0 [Subs] Custom app icons (#6758)
* custom icons

* rm default

* clouds.jpg

* use cross-platform fork

* minor fixes for android

* update dynamic icon lib

* gate app icon settings behind discover debug dids

* rename clouds

* Bop it

* Update default ios icon as well

* Remove old icon

* Update logo placement

* update to latest expo-dynamic-app-icon

* fix android icon sizes

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-11-27 14:41:18 +00:00
Samuel Newman c7d57b6374 css color transitions on left nav (#6768) 2024-11-27 14:34:07 +00:00
Frudrax Cheng 91fffd3cd2 Fix ast (#6774) 2024-11-27 14:17:44 +00:00
Samuel Newman b2c232e9fd bump version to 1.96 (#6781) 2024-11-27 14:12:04 +00:00
Paul Frazee 2a575addba Fix import sort 2024-11-26 19:32:19 -08:00
Paul Frazee 6d290591da Release 1.95 take2 (#6771)
* Run intl extract

* Update join count
2024-11-26 19:27:44 -08:00
Softastur 1edebc5dc7 Add "ast" locale support (#6411)
* Add "ast" locale support

* Add "ast" locale support

* Add "ast" locale support

* Add "ast" locale support

* Add "ast" locale support

* Add "ast" locale support

* Add "ast" translations

This commit adds preliminary translation support for "ast" locale. Around 20% is translated.

* Update "ast" locale translations

* Update "ast" translations

Fixes minor spelling mistakes and translates many strings more

* Update messages.po

With this commit we reach the 55% of strings translated after the last code sync

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
2024-11-26 19:14:43 -08:00
Juan Pablo Martínez 5929c2678c Add Aragonese language ('an') support and l10n (#6638)
* Update languages.ts

* Update i18n.web.ts

* Update i18n.ts

* Update helpers.ts

* Create messages.po

* Update lingui.config.js

* Update dates.ts

* Add files via upload

* Update src/components/hooks/dates.ts

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

* Update src/components/hooks/dates.ts

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

* Update i18n.ts

* Update messages.po

* Update src/locale/i18n.ts

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

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2024-11-26 19:06:47 -08:00
Ivan Beà 8cbe68306d Update catalan messages.po (#6682)
* Update catalan messages.po

Back to 100% translated.
Applied pending corrections by @darcio and @jordimas https://static.softcatala.org/quality/bluesky_social_app.html

Please take a look at it @ivanbea @jordimas @darccio @surfdude29 @rortan134 @magarcia

* Update catalan messages.po

Change all "arxiu" to "fitxer" as suggested by @jordimas

* Update catalan messages.po

Suggestions by @aryiu
2024-11-26 19:03:19 -08:00
Michele Locati 95e1b21788 Update Italian translations (#6717) 2024-11-26 19:02:09 -08:00
Guido Leenders b9db950d52 Informal tone and other improvements to Dutch (#6724)
* Informal tone [ER].

* More improvements [ER].

* Improvements by Flitskikker [ER].
2024-11-26 19:01:02 -08:00
Nando eb9dfba9f4 Improve German translation (#6750) 2024-11-26 18:54:18 -08:00
Frudrax Cheng 7860b790dc Update Chinese & Cantonese localization (#6754)
* Update translates from crowdin

* zh-CN: Re-translate 'Welcome, friend!'

* zh-CN: Improved translations

* Fix translate 'Threaded'

* zh-CN: Improve spelling and grammar

* Translated 'Allow quote posts'

* zh-CN: Better translation of 'from'

* zh-CN: Improved quantifiers

* zh-CN: Improved translations

* Update translates from crowdin

* Update translates from crowdin

* Improved translation
2024-11-26 18:53:44 -08:00
Samuel Newman 8825d6b1a7 Lightbox status bar behaviour (#6637)
* lightbox status bar

* add hideTransitionAnimation="slide"

* move navigation bar logic to util
2024-11-26 23:59:31 +00:00
dan 2c2d12b394 Fix stutter when swiping feeds on Android (#6765) 2024-11-26 23:56:57 +00:00
Samuel Newman 736ea3f49d Fix Android keyboard bug when opening a sheet (#6739)
* reimplement KeyboardStickyView

* fix missing arg
2024-11-26 15:57:21 +00:00
Samuel Newman ce1b04b925 Fix <List> types (#6756)
* fix List and ScrollView types

* add comment

* rm omitting ref
2024-11-26 15:55:27 +00:00
rshigg 56a88098d2 focus search input on soft reset in mobile (#5732) 2024-11-25 22:04:06 +00:00
dan 61e97d6962 Fix camera orientation on Android (#6729) 2024-11-25 21:08:14 +00:00
dan 9d3bdbb2b9 Edit string to avoid churning translations (#6737) 2024-11-25 20:49:43 +00:00
dan c6c6c91d7b Revert "Pass referrer on native (with an opt out) (#6648)" (#6732)
This reverts commit ac5b2cf31f.
2024-11-25 20:30:33 +00:00
Samuel Newman b0c36383a9 Ensure react-native-keyboard-controller enabled state doesn't get overwritten (#6727)
* revert to prev state instead of false

* add dep array

* use ref counting approach

* patch keyboard controller to allow changing the enabled prop

* remove state from patch

* change patched prop name

* remove Math.max check, log if < 0

* use noop provider

* rm patch, use `useRef`

* Style nits

* Rm on web

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-25 20:30:15 +00:00
Adam Rowe 6c81090021 Dismiss keyboard on drag in starter pack screens (#6537) 2024-11-25 19:04:28 +00:00
Kirill Zyusko 723bbfc58a fix: keyboard handler memoization (#6719)
* fix: keyboard handler memoization

* fix: return missing dependency
2024-11-25 15:03:55 +00:00
Evan 5164ca6288 Make quote post enabled switch label text static (#6720)
* Make quote post enabled switch label text static

* Update PostInteractionSettingsDialog.tsx

* meh this is better

---------

Co-authored-by: dan <dan.abramov@gmail.com>
2024-11-25 13:24:00 +00:00
dan b9406aa011 Don't re-sort already fetched post thread items (#6698)
* Don't reorder already seen posts in PostThread

* Add sorting by generation

* Rip out stable order cache

It doesn't make sense because sort() doesn't call the callback for all A/B pairs, and the server returning a different ordering will cause cache misses which means there'll be no stability anyway.

* Make hotness deterministic per fetched at

* Cache random scores while in thread

* Reorder for clarity
2024-11-24 19:10:54 +00:00
surfdude29 371e6ad333 Fix glitches in gl/messages.po (#6702)
* fix glitches in gl/messages.po

* Fix italian

---------

Co-authored-by: dan <dan.abramov@gmail.com>
2024-11-24 17:41:36 +00:00
dan a21e2b45db Fix format number polyfill for gl locale (#6680) 2024-11-24 17:15:08 +00:00
Paul Frazee 8c43acc1d3 Run intl:extract (#6678) 2024-11-23 16:42:43 -08:00
Paul Frazee 7782517a91 Merge branch 'monty241-main' into main 2024-11-23 16:34:31 -08:00
Paul Frazee a55444a53a Add 'nl' to lingui config 2024-11-23 16:34:11 -08:00
Paul Frazee 8fc4f68e69 Update src/locale/i18n.ts 2024-11-23 16:21:36 -08:00
Paul Frazee 32bf8122e8 Merge branch 'main' into main 2024-11-23 16:20:24 -08:00
Gonzalo Riestra b4d07c4112 Improve spanish translation of repost (#6395)
Use "republicar" instead of "volver a publicar"
2024-11-23 15:57:54 -08:00
Claudiu Cristea 1e168fd1e1 There’s no Moldavian/Moldovan language (#6427) 2024-11-23 15:56:21 -08:00
xurxogp 7e7910edf7 Added Galician language localization (#6463)
* Update languages.ts

added galician language

* Update i18n.web.ts

added import of galician translations file

* Update i18n.ts

added galician language

* Update helpers.ts

added galician to sanitizeLanguage function

* galician messages

First commit

* Second commit

* Third commit

* fourth commit

* fifth commit

* Update lingui.config.js

change to make available Galician language

* galician translation by Carlos Vieito and Alexandre Espinosa

---------

Co-authored-by: Alexandre Espinosa Menor <aemenor@gmail.com>
2024-11-23 15:54:59 -08:00
Martin Garcia af4db872b5 Update catalan messages.po (#6485)
* Update catalan messages.po

* Update src/locale/locales/ca/messages.po

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

* Update src/locale/locales/ca/messages.po

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

* Update src/locale/locales/ca/messages.po

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

* Update src/locale/locales/ca/messages.po

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

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2024-11-23 15:53:38 -08:00
Michele Locati 54d52b8172 Update Italian translations (#6487)
* Update Italian translations

* Fix typo

* Update messages.po

* yarn intl:extract

* Update italian translation

* Fix typo

* In Italy we almost never use Title Case

* @marcomaroni-github suggestions

* Add more strings, remove "per favore"

* Update narrow "# hours"

* Fix plural rules, translate remaining strings

* Update translations with @arestifop suggestions

* Update translations

* Update translations

---------

Co-authored-by: Lucaterzo <49860098+Lucaterzo@users.noreply.github.com>
Co-authored-by: Alessio <alessio.restifo@gmail.com>
2024-11-23 15:51:57 -08:00
Minseo Lee 1059088cf1 Update Korean localization (#6523)
* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po
2024-11-23 15:51:00 -08:00
Quang Phan 796d04b59f Vietnamese Localization (Continued from #2524) (#6564)
* Add Vietnamese localization

* Complete Vietnamese Translations

* Add date localization for Vietnamese

* Fix typos and wording in Vietnamese translation

* Update Vietnamese translation

* Add @vinhphm to Vietnamese translator list

---------

Co-authored-by: CromNguyen <nguyenthietmanh98@gmail.com>
2024-11-23 15:50:32 -08:00
FalKoopa 9bf7e278e7 Updated Hindi UI for Version 1.94 (#6566)
* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po
2024-11-23 15:49:39 -08:00
cereza d0089a9335 updated & added missing strings in FR locale (#6596)
* updated FR locale

* removed my name from Language-Team

i'm not sure if this is a list of every translation contributors or if this is the main people who handle translations, so i've decided to remove it for the time being

* replaced "fil d'actu Following" with "fil des abonnements"

i thought i did that already oops

* fixed DID mistranslation

i thought it stood for Direct Inward Dialing

* applied suggestions from @signez

Co-authored-by: Stanislas Signoud <signez@stanisoft.net>

* replaced spaces to non breaking spaces where necessary

might have some missing, i'll patch these up while fixing apostrophes

* fixed apostrophes

i also swapped "Changer l'e-mail" with "Changer votre e-mail", thought since it was an account-related thing, IIRC most of the account settings (handle, password, etc.) are possessive towards the user so i thought it'd be better to keep it consistent

* removed the period at the start

why is that here get that outta here

Co-authored-by: Stanislas Signoud <signez@stanisoft.net>

* applied suggestions from @surfdude29

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Stanislas Signoud <signez@stanisoft.net>

* Fix the remaning 'like' mentions in French

---------

Co-authored-by: Stanislas Signoud <signez@stanisoft.net>
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2024-11-23 15:48:52 -08:00
codehead 21a5140704 Translate missing strings. (#6599)
Co-authored-by: Javier Arturo Rodriguez <javier@rodriguez.org.mx>
2024-11-23 15:47:42 -08:00
Luan 468de4c6db Update Portuguese localization (#6603)
* Update PT-BR messages.po

* Apply suggestions from code review

Co-authored-by: Paul Frazee <pfrazee@gmail.com>

* Apply suggestions from code review

* Apply suggestions from code review

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
2024-11-23 15:46:38 -08:00
Frudrax Cheng 260eb59530 Update Chinese & Cantonese localization (#6626)
* Update translates from crowdin

* Completed the translations for video and HTTP referer control

* Improved translation

* Fix several translation issues

* Improved Cantonese translations
2024-11-23 15:14:53 -08:00
Kevin Scannell 69e5174057 Update Irish translation (#6668) 2024-11-23 15:12:22 -08:00
dan 9f070834dd Fix skeleton alignment (#6676) 2024-11-23 22:10:53 +00:00
dan aa58216b6d Fix jumpy web animation for modal backdrop (#6673) 2024-11-23 21:36:35 +00:00
Nick Perez fdc3f0f17d perf: optimizations for composer with Tiptap (#6315)
* chore: update tiptap packages

* perf: reduce number of re-renders

* refactor: apply min height without a layout effect

* Remove stale zeed-dom resolution

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-23 21:07:37 +00:00
Khuddite 0395ba3e79 derive bottom padding from header height for profiles list (#6152) 2024-11-23 20:20:09 +00:00
Bas van den Wollenberg b892d8be08 Fix media query used to prevent enter sending DM on mobile web (#6669) 2024-11-23 20:11:13 +00:00
Luke Lucas 9663b3e855 fix: nothing selected bug (#6260)
* fix: nothing selected bug

* fix: consolidate related logic for audience settings

* simplify

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-23 20:07:44 +00:00
Khuddite f802f81fa6 Fix broken pagination for lists tab on profile page (#6221)
* set onEndReachedThreshold to 2 for ProfileLists

* Add a footer for ProfileLists component for consistency

* Remove logs

* Revert onEndReachedThreshold change on Feed component

* Fix threshold defaults to match native

* Align lists and feedgens

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-23 19:52:36 +00:00
Roland Crosby 30af267bb4 Fix hovered repost button styling when logged out (#6222) 2024-11-23 19:17:29 +00:00
dan b1c1c49897 Fix avi button hitslops (#6662)
* Remove web hack from non-web file

* Remove hitSlop on the wrong Pressable

It is not doing anything useful.

* Extend avi column to prevent hit rect clipping

You can't click outside the parent on Android.

* Bump pressed opacity to .8

* Slightly reduce avi button hitslops

* Asymmetric hit slop
2024-11-23 19:13:17 +00:00
Khuddite e428474478 Fix inconsistent number formatting between mobile and web (#6384)
* Manual truncation & identify factor points for each lang

* Reduce indirection

* Add test

Co-authored-by: khuddite <biliie811028@hotmail.com>

* Handle big numbers, clarify special case

* Clarify the reason

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-23 19:12:25 +00:00
Horus Lugo 4dd62e24ea Fix Newline handling in Web's Composer (#6658) 2024-11-23 17:34:16 +00:00
Frudrax Cheng d29599fa6c Fix translate button not working (#6657)
* Update PostThreadItem.tsx

* Fix in-app browser usage

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-23 17:25:14 +00:00
Trivikram Kamat e9a792e4c1 Bump @atproto/dev-env to align with @atproto/api (#6652) 2024-11-23 14:01:05 +00:00
dan 631277dc3d Add a hotness thread sort (#6649)
* Add a hotness thread sort

* Bump @atproto/api
2024-11-23 03:27:17 +00:00
dan ac5b2cf31f Pass referrer on native (with an opt out) (#6648)
* Pass referer on native

* Add ChainLink3

* Add an opt out for sending utm

* Remove noreferrer on links

We do have <meta name="referrer" content="origin-when-cross-origin"> in HTML, should be sufficient.

* Narrow down the condition slightly

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-11-22 23:28:45 +00:00
dan fee2f5daa2 Remove indirection, insert between slices (#6645) 2024-11-22 22:01:52 +00:00
dan ba04bb58cd Fix leaking background (#6642) 2024-11-22 19:51:36 +00:00
dan 7fa47ef3c9 Simplify play icon shadow (#6641)
* Simplify play icon shadow

* Make it consistent across themes
2024-11-22 19:43:32 +00:00
rshigg ced67876cc Add ability to block account from post dropdown menu (#6091)
Co-authored-by: Hailey <me@haileyok.com>
Co-authored-by: Hailey <hailey@blueskyweb.xyz>
2024-11-22 11:36:00 -08:00
Samuel Newman 247bc100cb Bump Typescript version to 5.7 (#6640) 2024-11-22 19:27:30 +00:00
Samuel Newman bc2a3aee0c fix avi follow deadzone (#6639) 2024-11-22 18:56:07 +00:00
Samuel Newman 3781074921 Add gif support to web (#6433)
* add gif support to web

* rm set dimensions

* rm effect from preview

* rm log

* rm use of {cause: error}

* fix lint
2024-11-22 17:58:29 +00:00
Samuel Newman 76ca72cf72 Dismissable toasts (#6345)
* dismissable toast

* adjust top offset

* improve a11y

* stretchy pull-down

* Dismiss web on tap

* Simplify code

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-22 16:11:59 +00:00
Samuel Newman 5d4aaa5b5f [a11y] Video - fix labels and make more detailed (#6635)
* fix labels and make more detailed

* move overall label to parent
2024-11-22 15:23:24 +00:00
Paul Coroneos 437bdcf9a9 Remove unnecessary lint rule suppression (#6544)
* ▫️remove deprecated react native eslint package. add react hooks and modern native eslint package. fix bugs

* in retrospect lets just remove the async from uselocalelanguage

* finally clean it all up

* revert yarn.lock

* restore logic
2024-11-22 15:22:48 +00:00
Samuel Newman d9ba324d7f Use android native animations (#6562) 2024-11-22 14:56:13 +00:00
Eric Bailey 72326844cd Improve error name and message for signed-in only feeds (#6577)
* Improve error name and message for logged-in only feeds

* Revert msg edit

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-22 14:45:59 +00:00
Samuel Newman a5dbb35237 Use outline style for repost cancel button (#6509)
* use outline style for repost cancel button

* use trans macro in JSX
2024-11-22 13:41:24 +00:00
dan a0acd514ab [Lightbox] Fix jump when zooming out on iOS (#6633) 2024-11-22 13:34:16 +00:00
Paul Coroneos 5028753367 Fix lightbox spinner (#6561)
* refactor imageitem useanimatedreaction logic

* revert copy/paste fix

* revert last commit

* Fix conditions

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-11-22 11:59:47 +00:00
surfdude29 058a29c6e3 Add CFBundleLocalizations key to Info.plist (#6567) 2024-11-22 11:34:37 +00:00
Frudrax Cheng 09ce65b24e Fix display language not switching correctly to Chinese on native. (#6621)
* Update deviceLocales.ts

* Update deviceLocales.ts
2024-11-22 11:32:25 +00:00
dan c858700c17 [Android] Lower Feed maxRenderPerBatch (#6624) 2024-11-22 04:35:53 +00:00
dan 625459add6 Fix some overdraw (#6617) 2024-11-21 23:40:37 +00:00
dan 391e0c4c18 Fix embeder (#6616) 2024-11-21 22:52:28 +00:00
dan dc3a42edb1 Reduce <Text> nesting (#6615)
* Move isOnlyEmoji out of RichText

To fix Fast Refresh.

* Make renderChildrenWithEmoji work with any children

* Always go through UITextView for consistency

It already contains the `selectable` and `iOS` checks inside.

* Move `emoji` check into `renderChildrenWithEmoji`

* Remove unnecessary intermediate UITextView nodes

* Make childHasEmoji check recursive

It didn't handle nested arrays etc correctly before.

* Remove the "children must be string" limitation

Should not be necessary now that we correctly handle nested arrays etc.

* Fix unnecessary regex reallocation

This doesn't have a global flag so it's okay to reuse.

* Remove unnecessary <Text> wrapper in RichText
2024-11-21 22:52:16 +00:00
dan 84724bb940 [Android] Patch react-native-svg to cache parsed paths (#6583) 2024-11-21 22:32:49 +00:00
Samuel Newman 9f1e648686 Fix overflow issue on iOS autocomplete, among other things (#6611)
* stop using ref in render

* fix display name fallback on web

* use unicode ellipsis for useGrapheme

* fix overflow issue

* sanitize handle/displayname on web
2024-11-21 22:32:32 +00:00
Aditya Mathur f67e00a85c fix: reused getRkey function in embed component (#6591) 2024-11-21 21:10:19 +00:00
jviide 48cda1350e Dedup zod versions in bskyembed/yarn.lock (#6610) 2024-11-21 20:45:40 +00:00
dan cc60566cde Fix Fast Refresh in <Text> files (#6609)
* Separate non-components from components

* Mark old Text as deprecated

* Move emoji utilities to non-React file

* Fix type

* Fix import
2024-11-21 20:44:40 +00:00
dan ff23ddb556 Don't underline links on native hover (#6588) 2024-11-21 19:20:46 +00:00
lauren dff11fc076 Pin exact version of React Compiler packages (#6607)
This PR pins the various React Compiler packages to their exact
versions. While it is typically safe to upgrade the compiler, we
recommend manual upgrades because changes in memoization may or may not
be unexpected.

In the near future we will open source an upgrade script that automates
comparing the current compiled output with the upgraded output and
prints a diff for human confirmation prior to committing the upgrade.
2024-11-21 17:40:39 +00:00
dan ad83b8303e Remove unused RN.Animated code (#6584)
* Remove unused RN.Animated code

* Rm more dead code
2024-11-21 12:10:34 +00:00
dan 6438ec6d28 Disable vertical scrollbars on Android only (#6586) 2024-11-21 12:10:16 +00:00
Samuel Newman 97a4ff1a4c Bump 1.95.0 (#6530) 2024-11-21 01:25:25 +00:00
Horus Lugo 6605970fcf Prevent ProfileHeaderHandle from breaking out of its parent view (#6574) 2024-11-20 21:47:06 +00:00
Samuel Newman 202df845f3 rm unused type lib (#6546) 2024-11-20 16:21:18 +00:00
Samuel Newman 0e4f6717ce fix change handle dialog on android (#6563) 2024-11-20 13:26:23 +00:00
Trivikram Kamat f1c221ed28 Bump typescript-eslint to 7.x (#6525)
* chore(deps-dev): bump typescript-eslint to 6.x

* chore(deps-dev): switch to @react-native/eslint-config

* chore(deps-dev): bump typescript-eslint to 7.x
2024-11-20 02:23:26 +00:00
Andrew Aquino 78e34c8d35 preserve video's last known time when scrolling away (#6239) 2024-11-19 22:42:25 +00:00
axiand f882cf9737 Implement Polish translation (#6283)
* Initial polish translation sweep

* rewrite feeds page flavor text

* bring up to date with dev branch, revisit some strings

* redoing some more...

* bring up to date

* rm dead strings, try out a different phrasing for threaded mode str

prev translation was nonsensical - we're already calling threads "wątki". let's try "recursive tree" instead to describe what threaded mode does

* tweaks here and there

* more tweaks. sign translation file

TL note: trying a new space-saving translation for send via direct message, old one made the context menu stretch

* space-saving translations for profile tabs

* up to date

* tl latest changes

* some small fixes

* few more tweaks around profile editing strings

* i dislike gendered languages

* yet more tweaks

* Update dates.ts

* Review suggestions (1)

* tiny fixes

* Review suggestions (2)

* Review suggestions (3)

* Review suggestions (4)

* "Anuluj wyciszenie"

* A few new strings

* Remove dead strings again

* Review suggestions (5)

* te -> to

---------

Co-authored-by: Frudrax Cheng <i@cynosura.one>
2024-11-19 12:24:18 -08:00
smileyhead c0c8a192ce Update Hungarian translation for 1.94 (#6186) 2024-11-19 12:17:10 -08:00
Evan 3c33e42d35 Fix post interaction selectable text being cut off (#6527) 2024-11-19 19:07:27 +00:00
Paul Coroneos 5d7cfa0206 Remove aria-pressed attr from button. Closes #6325 (#6402) 2024-11-19 19:07:13 +00:00
Samuel Newman 103dfe2145 add back expo-system-ui (#6529) 2024-11-19 19:02:28 +00:00
dan 31b0d7cede Render post menu items lazily (take two) (#6473)
* Revert "Revert "Render dropdown menu items lazily (#6437)" (#6470)"

This reverts commit 0b38c77faa.

* Lazily initialize menu items

* Split in two files

* Make it work on mobile

This is wonky because our useMenuDialog abstraction only has `isOpen` on web. I couldn't figure out a way to make it work xplat so I'm just tracking it myself manually.

* Fix typo in comment
2024-11-19 18:27:56 +00:00
Trivikram Kamat 39c9f7248e Remove usused direct dependencies on 'webpack' (#6466)
* Remove usused direct dependencies on 'webpack'

* Remove direct dependency on 'babel-plugin-react-native-web'
2024-11-19 15:45:09 +00:00
Trivikram Kamat 2b30b0f7c6 Remove unused 'lodash' dependencies (#6472) 2024-11-19 15:35:16 +00:00
Paul Coroneos ff5ad38253 Leverage jsx transform to remove unnecessary react imports and update eslint config (#6516)
* update eslint config with jsx runtime

* leverage jsx transform to remove unnecessary react imports and update eslint config

* run yarn lint --fix to remove eslint disables related to react/prop-types that is now disabled
2024-11-19 15:33:07 +00:00
dan cfc653a5f4 Split FeedSlice into FlatList rows (#6507) 2024-11-19 15:24:08 +00:00
Samuel Newman 8d5e61e1e5 Use native modal for signup queue screen (#6392)
* fix top inset for signup queue

* use native modal for queue screen

* spacing for if text is super large

* don't allow scroll bounce
2024-11-19 14:43:40 +00:00
Samuel Newman ea3fe93be9 Attempted video perf improvements (#6519)
* remove layout animations from video

* only show button/spinner when necessary

* use native activityindicator rather than loader
2024-11-19 12:50:06 +00:00
dan 9a57d2a538 Add Profiler build for Android (#6510) 2024-11-19 02:20:58 +00:00
dan 7b6c182723 Add useHandleRef as a lighter alternative for useAnimatedRef (#6500) 2024-11-18 22:21:47 +00:00
dan 4f0f9eb413 Fix TagMenu initial render perf (#6483)
* Split tag menu dialog content

* Log the actual error please

* Fix it on native
2024-11-18 22:05:51 +00:00
Martin Pittenauer 1486e5cc5a Help Siri to correctly pronounce Blue Sky. (#6496)
* Add CFBundleSpokenName

Help Siri to correctly pronounce Blue Sky. Instead of pronouncing it like “Lebowski”.

* Format

---------

Co-authored-by: dan <dan.abramov@gmail.com>
2024-11-18 21:55:35 +00:00
dan ee49ca7a3d Hardcode Giphy embed height (#6475) 2024-11-18 21:55:20 +00:00
dan b87c94e6d9 Extract RepostButton inner dialog (#6498)
* Extract RepostButton inner dialog

* use `useDialogContext` instead of passing prop

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2024-11-18 21:51:23 +00:00
dan ec97352201 Fix layout jumps for images (#6474) 2024-11-18 21:42:32 +00:00
dan 924f1e0e7f Optimize ContentHider (#6501) 2024-11-18 17:44:45 +00:00
Guido Leenders 523d1f01a5 Change translation of "appeal" [ER]. 2024-11-18 15:07:04 +01:00
dan 0b38c77faa Revert "Render dropdown menu items lazily (#6437)" (#6470)
This reverts commit e9fe8d90ef.
2024-11-17 21:06:22 +00:00
Guido Leenders 93dec879ad Included review remarks by SleeplessByte [ER]. 2024-11-17 21:10:49 +01:00
Guido Leenders f139bcbcd4 Fix typo [PR]. 2024-11-14 10:52:08 +01:00
Guido Leenders 2cb6e713bb First completion [ER]. 2024-11-14 10:49:23 +01:00
Guido Leenders 6aa44d681d More Dutch translations [ER]. 2024-11-14 10:05:30 +01:00
Guido Leenders 1b87351d38 More Dutch translations [ER]. 2024-11-13 23:48:45 +01:00
Guido Leenders ef14faaa58 More Dutch translations [ER]. 2024-11-13 17:23:04 +01:00
Guido Leenders 54a4a6c161 More Dutch translations [ER]. 2024-11-13 14:17:36 +01:00
Guido Leenders 8754527aa6 More Dutch translations [ER]. 2024-11-13 12:15:15 +01:00
Guido Leenders be429d5750 More translations [ER]. 2024-11-12 19:48:17 +01:00
Guido Leenders 874a624e25 Add first translation of Dutch [ER]. 2024-11-12 16:19:30 +01:00
Guido Leenders 5910f78329 Revert changes to packages.json [PR]. 2024-11-12 10:00:01 +01:00
Guido Leenders b6fdde2f04 Allow use of Dutch translations [ER]. 2024-11-12 09:45:10 +01:00
333 changed files with 75297 additions and 24212 deletions
+3 -3
View File
@@ -1,8 +1,9 @@
module.exports = {
root: true,
extends: [
'@react-native-community',
'@react-native',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-native-a11y/ios',
'prettier',
],
@@ -16,9 +17,8 @@ module.exports = {
'eslint-plugin-react-compiler',
],
rules: {
// Temporary until https://github.com/facebook/react-native/pull/43756 gets into a release.
'prettier/prettier': 0,
'react/no-unescaped-entities': 0,
'react/prop-types': 0,
'react-native/no-inline-styles': 0,
'bsky-internal/avoid-unwrapped-text': [
'error',
+94 -2
View File
@@ -71,7 +71,7 @@ module.exports = function (config) {
policy: 'appVersion',
},
orientation: 'portrait',
icon: './assets/icon.png',
icon: './assets/app-icons/ios_icon_default_light.png',
userInterfaceStyle: 'automatic',
splash: SPLASH_CONFIG,
// hsl(211, 99%, 53%), same as palette.default.brandText
@@ -92,6 +92,31 @@ module.exports = function (config) {
'Used to save images to your library.',
NSPhotoLibraryUsageDescription:
'Used for profile pictures, posts, and other kinds of content',
CFBundleSpokenName: 'Blue Sky',
CFBundleLocalizations: [
'en',
'ca',
'de',
'es',
'fi',
'fr',
'ga',
'hi',
'hu',
'id',
'it',
'ja',
'ko',
'pl',
'pt',
'ru',
'th',
'tr',
'uk',
'zh_CN',
'zh_HK',
'zh_TW',
],
},
associatedDomains: ASSOCIATED_DOMAINS,
splash: {
@@ -137,7 +162,7 @@ module.exports = function (config) {
backgroundColor: DARK_SPLASH_CONFIG_ANDROID.backgroundColor,
},
android: {
icon: './assets/icon.png',
icon: './assets/app-icons/android_icon_default_light.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-foreground.png',
@@ -248,6 +273,73 @@ module.exports = function (config) {
],
},
],
[
'@mozzius/expo-dynamic-app-icon',
{
/**
* Default set
*/
default_light: {
ios: './assets/app-icons/ios_icon_default_light.png',
android: './assets/app-icons/android_icon_default_light.png',
prerendered: true,
},
default_dark: {
ios: './assets/app-icons/ios_icon_default_dark.png',
android: './assets/app-icons/android_icon_default_dark.png',
prerendered: true,
},
/**
* Bluesky+ core set
*/
core_aurora: {
ios: './assets/app-icons/ios_icon_core_aurora.png',
android: './assets/app-icons/android_icon_core_aurora.png',
prerendered: true,
},
core_bonfire: {
ios: './assets/app-icons/ios_icon_core_bonfire.png',
android: './assets/app-icons/android_icon_core_bonfire.png',
prerendered: true,
},
core_sunrise: {
ios: './assets/app-icons/ios_icon_core_sunrise.png',
android: './assets/app-icons/android_icon_core_sunrise.png',
prerendered: true,
},
core_sunset: {
ios: './assets/app-icons/ios_icon_core_sunset.png',
android: './assets/app-icons/android_icon_core_sunset.png',
prerendered: true,
},
core_midnight: {
ios: './assets/app-icons/ios_icon_core_midnight.png',
android: './assets/app-icons/android_icon_core_midnight.png',
prerendered: true,
},
core_flat_blue: {
ios: './assets/app-icons/ios_icon_core_flat_blue.png',
android: './assets/app-icons/android_icon_core_flat_blue.png',
prerendered: true,
},
core_flat_white: {
ios: './assets/app-icons/ios_icon_core_flat_white.png',
android: './assets/app-icons/android_icon_core_flat_white.png',
prerendered: true,
},
core_flat_black: {
ios: './assets/app-icons/ios_icon_core_flat_black.png',
android: './assets/app-icons/android_icon_core_flat_black.png',
prerendered: true,
},
core_classic: {
ios: './assets/app-icons/ios_icon_core_classic.png',
android: './assets/app-icons/android_icon_core_classic.png',
prerendered: true,
},
},
],
].filter(Boolean),
extra: {
eas: {
Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 714 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

+2 -3
View File
@@ -9,7 +9,6 @@ import {
AppBskyGraphDefs,
AppBskyGraphStarterpack,
AppBskyLabelerDefs,
AtUri,
} from '@atproto/api'
import {ComponentChildren, h} from 'preact'
import {useMemo} from 'preact/hooks'
@@ -437,14 +436,14 @@ function StarterPackEmbed({
// from #/lib/strings/starter-pack.ts
function getStarterPackImage(starterPack: AppBskyGraphDefs.StarterPackView) {
const rkey = new AtUri(starterPack.uri).rkey
const rkey = getRkey({uri: starterPack.uri})
return `https://ogcard.cdn.bsky.app/start/${starterPack.creator.did}/${rkey}`
}
function getStarterPackHref(
starterPack: AppBskyGraphDefs.StarterPackViewBasic,
) {
const rkey = new AtUri(starterPack.uri).rkey
const rkey = getRkey({uri: starterPack.uri})
const handleOrDid = starterPack.creator.handle || starterPack.creator.did
return `/starter-pack/${handleOrDid}/${rkey}`
}
+1 -6
View File
@@ -4209,12 +4209,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod@^3.21.4:
version "3.22.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
zod@^3.23.8:
zod@^3.21.4, zod@^3.23.8:
version "3.23.8"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d"
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
+1
View File
@@ -257,6 +257,7 @@ func serve(cctx *cli.Context) error {
e.GET("/settings/privacy-and-security", server.WebGeneric)
e.GET("/settings/content-and-media", server.WebGeneric)
e.GET("/settings/about", server.WebGeneric)
e.GET("/settings/app-icon", server.WebGeneric)
e.GET("/sys/debug", server.WebGeneric)
e.GET("/sys/debug-mod", server.WebGeneric)
e.GET("/sys/log", server.WebGeneric)
+10 -2
View File
@@ -27,13 +27,21 @@
let player
function onYouTubeIframeAPIReady() {
let videoId = url.searchParams.get('videoId')
videoId = decodeURIComponent(videoId)
videoId = videoId.replace(/[^a-zA-Z0-9_-]/g, "")
if (videoId.length !== 11) throw new Error("Invalid video ID")
let start = url.searchParams.get('start')
start = start.replace(/[^0-9]/g, "")
player = new YT.Player('player', {
width: "1000",
height: "1000",
videoId: url.searchParams.get('videoId'),
videoId,
playerVars: {
autoplay: 1,
start: url.searchParams.get('start'),
start,
rel: 0,
loop: 0,
playsinline: 1,
+1 -1
View File
@@ -40,7 +40,6 @@
}
html {
background-color: white;
scrollbar-gutter: stable both-edges;
}
@media (prefers-color-scheme: dark) {
html {
@@ -79,6 +78,7 @@
/* We need this style to prevent web dropdowns from shifting the display when opening */
body {
width: 100%;
overflow-y: scroll;
}
</style>
+6
View File
@@ -2,6 +2,8 @@
module.exports = {
locales: [
'en',
'ast',
'an',
'ca',
'de',
'en-GB',
@@ -9,17 +11,21 @@ module.exports = {
'fi',
'fr',
'ga',
'gl',
'hi',
'hu',
'id',
'it',
'ja',
'ko',
'nl',
'pl',
'pt-BR',
'ru',
'th',
'tr',
'uk',
'vi',
'zh-CN',
'zh-HK',
'zh-TW',
+14
View File
@@ -9,6 +9,11 @@ cfg.resolver.sourceExts = process.env.RN_SRC_EXT
if (cfg.resolver.resolveRequest) {
throw Error('Update this override because it is conflicting now.')
}
if (process.env.BSKY_PROFILE) {
cfg.cacheVersion += ':PROFILE'
}
cfg.resolver.resolveRequest = (context, moduleName, platform) => {
// HACK: manually resolve a few packages that use `exports` in `package.json`.
// A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing.
@@ -29,6 +34,15 @@ cfg.resolver.resolveRequest = (context, moduleName, platform) => {
if (moduleName === '@ipld/dag-cbor') {
return context.resolveRequest(context, '@ipld/dag-cbor/src', platform)
}
if (process.env.BSKY_PROFILE) {
if (moduleName.endsWith('ReactNativeRenderer-prod')) {
return context.resolveRequest(
context,
moduleName.replace('-prod', '-profiling'),
platform,
)
}
}
return context.resolveRequest(context, moduleName, platform)
}
+31 -47
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.94.0",
"version": "1.96.0",
"private": true,
"engines": {
"node": ">=20"
@@ -11,6 +11,8 @@
"postinstall": "patch-package && yarn intl:compile",
"prebuild": "expo prebuild --clean",
"android": "expo run:android",
"android:prod": "expo run:android --variant release",
"android:profile": "BSKY_PROFILE=1 expo run:android --variant release",
"ios": "expo run:ios",
"web": "expo start --web",
"use-build-number": "./scripts/useBuildNumberEnv.sh",
@@ -23,7 +25,6 @@
"build-embed": "cd bskyembed && yarn build && yarn build-snippet && cd .. && node ./scripts/post-embed-build.js",
"start": "expo start --dev-client",
"start:prod": "expo start --dev-client --no-dev --minify",
"clean-cache": "rm -rf node_modules/.cache/babel-loader/*",
"test": "NODE_ENV=test jest --forceExit --testTimeout=20000 --bail",
"test-watch": "NODE_ENV=test jest --watchAll",
"test-ci": "NODE_ENV=test jest --ci --forceExit --reporters=default --reporters=jest-junit",
@@ -53,7 +54,7 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
"@atproto/api": "^0.13.11",
"@atproto/api": "^0.13.18",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
"@emoji-mart/react": "^1.1.1",
@@ -73,6 +74,7 @@
"@lingui/react": "^4.5.0",
"@mattermost/react-native-paste-input": "^0.7.1",
"@miblanchard/react-native-slider": "^2.3.1",
"@mozzius/expo-dynamic-app-icon": "^1.4.1",
"@radix-ui/react-dismissable-layer": "^1.1.1",
"@radix-ui/react-dropdown-menu": "2.0.1",
"@radix-ui/react-focus-guards": "^1.1.1",
@@ -89,18 +91,18 @@
"@tanstack/query-async-storage-persister": "^5.25.0",
"@tanstack/react-query": "^5.8.1",
"@tanstack/react-query-persist-client": "^5.25.0",
"@tiptap/core": "^2.6.6",
"@tiptap/extension-document": "^2.6.6",
"@tiptap/extension-hard-break": "^2.6.6",
"@tiptap/extension-history": "^2.6.6",
"@tiptap/extension-mention": "^2.6.6",
"@tiptap/extension-paragraph": "^2.6.6",
"@tiptap/extension-placeholder": "^2.6.6",
"@tiptap/extension-text": "^2.6.6",
"@tiptap/html": "^2.6.6",
"@tiptap/pm": "^2.6.6",
"@tiptap/react": "^2.6.6",
"@tiptap/suggestion": "^2.6.6",
"@tiptap/core": "^2.9.1",
"@tiptap/extension-document": "^2.9.1",
"@tiptap/extension-hard-break": "^2.9.1",
"@tiptap/extension-history": "^2.9.1",
"@tiptap/extension-mention": "^2.9.1",
"@tiptap/extension-paragraph": "^2.9.1",
"@tiptap/extension-placeholder": "^2.9.1",
"@tiptap/extension-text": "^2.9.1",
"@tiptap/html": "^2.9.1",
"@tiptap/pm": "^2.9.1",
"@tiptap/react": "^2.9.1",
"@tiptap/suggestion": "^2.9.1",
"@types/invariant": "^2.2.37",
"@types/lodash.throttle": "^4.1.9",
"@types/node": "^20.14.3",
@@ -140,6 +142,7 @@
"expo-sharing": "^12.0.1",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
"expo-system-ui": "^3.0.4",
"expo-task-manager": "~11.8.1",
"expo-updates": "~0.25.14",
"expo-web-browser": "~13.0.3",
@@ -152,11 +155,6 @@
"lodash.chunk": "^4.2.0",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0",
"lodash.omit": "^4.5.0",
"lodash.once": "^4.1.1",
"lodash.random": "^3.2.0",
"lodash.samplesize": "^4.2.0",
"lodash.set": "^4.3.2",
"lodash.shuffle": "^4.2.0",
"lodash.throttle": "^4.1.1",
"multiformats": "^13.1.0",
@@ -166,7 +164,7 @@
"postinstall-postinstall": "^2.1.0",
"psl": "^1.9.0",
"react": "18.2.0",
"react-compiler-runtime": "^19.0.0-beta-a7bf2bd-20241110",
"react-compiler-runtime": "19.0.0-beta-a7bf2bd-20241110",
"react-dom": "^18.2.0",
"react-image-crop": "^11.0.7",
"react-keyed-flatten-children": "^3.0.0",
@@ -196,6 +194,7 @@
"react-native-web": "~0.19.11",
"react-native-web-webview": "^1.0.2",
"react-native-webview": "13.10.2",
"react-remove-scroll": "^2.6.0",
"react-responsive": "^9.0.2",
"react-textarea-autosize": "^8.5.3",
"rn-fetch-blob": "^0.12.0",
@@ -207,7 +206,7 @@
"zod": "^3.20.2"
},
"devDependencies": {
"@atproto/dev-env": "^0.3.55",
"@atproto/dev-env": "^0.3.64",
"@babel/core": "^7.23.2",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
@@ -217,45 +216,35 @@
"@lingui/cli": "^4.5.0",
"@lingui/macro": "^4.5.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@react-native-community/eslint-config": "^3.0.0",
"@react-native/eslint-config": "^0.76.2",
"@react-native/typescript-config": "^0.74.1",
"@testing-library/jest-native": "^5.4.1",
"@testing-library/react-native": "^11.5.2",
"@tsconfig/react-native": "^2.0.3",
"@types/he": "^1.1.2",
"@types/jest": "^29.4.0",
"@types/lodash.chunk": "^4.2.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.isequal": "^4.5.6",
"@types/lodash.omit": "^4.5.7",
"@types/lodash.once": "^4.1.7",
"@types/lodash.random": "^3.2.7",
"@types/lodash.samplesize": "^4.2.7",
"@types/lodash.set": "^4.3.7",
"@types/lodash.shuffle": "^4.2.7",
"@types/psl": "^1.1.1",
"@types/react-dom": "^18.2.18",
"@types/react-responsive": "^8.0.5",
"@types/react-test-renderer": "^17.0.1",
"@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.48.2",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"babel-jest": "^29.7.0",
"babel-loader": "^9.1.2",
"babel-plugin-macros": "^3.1.0",
"babel-plugin-module-resolver": "^5.0.0",
"babel-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
"babel-plugin-react-native-web": "^0.18.12",
"babel-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"babel-preset-expo": "^10.0.0",
"eslint": "^8.19.0",
"eslint-plugin-bsky-internal": "link:./eslint",
"eslint-plugin-ft-flow": "^2.0.3",
"eslint-plugin-lingui": "^0.2.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-compiler": "^19.0.0-beta-a7bf2bd-20241110",
"eslint-plugin-react-compiler": "19.0.0-beta-a7bf2bd-20241110",
"eslint-plugin-react-native-a11y": "^3.3.0",
"eslint-plugin-simple-import-sort": "^12.0.0",
"file-loader": "6.2.0",
"html-webpack-plugin": "^5.5.0",
"husky": "^8.0.3",
"is-ci": "^3.0.1",
"jest": "^29.7.0",
@@ -269,21 +258,16 @@
"react-test-renderer": "18.2.0",
"svgo": "^3.3.2",
"ts-node": "^10.9.1",
"typescript": "^5.5.4",
"url-loader": "^4.1.1",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1"
"typescript": "^5.7.2",
"webpack-bundle-analyzer": "^4.10.1"
},
"resolutions": {
"@radix-ui/react-focus-scope": "1.1.0",
"@react-native/babel-preset": "0.74.1",
"@types/react": "^18",
"**/zeed-dom": "0.10.9",
"**/zod": "3.23.8",
"**/expo-constants": "16.0.1",
"**/expo-device": "6.0.2",
"@react-native/babel-preset": "0.74.1",
"@radix-ui/react-focus-scope": "1.1.0"
"**/zod": "3.23.8"
},
"jest": {
"preset": "jest-expo/ios",
@@ -0,0 +1,39 @@
diff --git a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
index 5de0845..fa477e7 100644
--- a/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
+++ b/node_modules/react-native-image-crop-picker/android/src/main/java/com/reactnative/ivpusic/imagepicker/PickerModule.java
@@ -21,6 +21,7 @@ import android.webkit.MimeTypeMap;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
+import androidx.exifinterface.media.ExifInterface;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Callback;
@@ -678,6 +679,15 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
throw new Exception("Cannot select remote files");
}
BitmapFactory.Options original = validateImage(path);
+ ExifInterface originalExif = new ExifInterface(path);
+ int orientation = originalExif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
+ boolean invertDimensions = (
+ orientation == ExifInterface.ORIENTATION_ROTATE_90 ||
+ orientation == ExifInterface.ORIENTATION_ROTATE_270 ||
+ orientation == ExifInterface.ORIENTATION_TRANSPOSE ||
+ orientation == ExifInterface.ORIENTATION_TRANSVERSE
+ );
+
// if compression options are provided image will be compressed. If none options is provided,
// then original image will be returned
@@ -687,8 +697,8 @@ class PickerModule extends ReactContextBaseJavaModule implements ActivityEventLi
long modificationDate = new File(path).lastModified();
image.putString("path", "file://" + compressedImagePath);
- image.putInt("width", options.outWidth);
- image.putInt("height", options.outHeight);
+ image.putInt("width", invertDimensions ? options.outHeight : options.outWidth);
+ image.putInt("height", invertDimensions ? options.outWidth : options.outHeight);
image.putString("mime", options.outMimeType);
image.putInt("size", (int) new File(compressedImagePath).length());
image.putString("modificationDate", String.valueOf(modificationDate));
+57
View File
@@ -0,0 +1,57 @@
diff --git a/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java b/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
index 06829bd..1b15818 100644
--- a/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
+++ b/node_modules/react-native-svg/android/src/main/java/com/horcrux/svg/PathView.java
@@ -14,17 +14,33 @@ import android.graphics.Paint;
import android.graphics.Path;
import com.facebook.react.bridge.ReactContext;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+class ParsedPath {
+ final Path path;
+ final ArrayList<PathElement> elements;
+
+ ParsedPath(Path path, ArrayList<PathElement> elements) {
+ this.path = path;
+ this.elements = elements;
+ }
+}
+
@SuppressLint("ViewConstructor")
class PathView extends RenderableView {
private Path mPath;
+ // This grows forever but for our use case (static icons) it's ok.
+ private static final HashMap<String, ParsedPath> sPathCache = new HashMap<>();
+
public PathView(ReactContext reactContext) {
super(reactContext);
PathParser.mScale = mScale;
mPath = new Path();
}
- public void setD(String d) {
+ void setDByParsing(String d) {
mPath = PathParser.parse(d);
elements = PathParser.elements;
for (PathElement elem : elements) {
@@ -33,6 +49,17 @@ class PathView extends RenderableView {
point.y *= mScale;
}
}
+ }
+
+ public void setD(String d) {
+ ParsedPath cached = sPathCache.get(d);
+ if (cached != null) {
+ mPath = cached.path;
+ elements = cached.elements;
+ } else {
+ setDByParsing(d);
+ sPathCache.put(d, new ParsedPath(mPath, elements));
+ }
invalidate();
}
+43 -35
View File
@@ -4,16 +4,17 @@ import '#/view/icons'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
import {KeyboardProvider} from 'react-native-keyboard-controller'
import {RootSiblingParent} from 'react-native-root-siblings'
import {
initialWindowMetrics,
SafeAreaProvider,
} from 'react-native-safe-area-context'
import * as SplashScreen from 'expo-splash-screen'
import * as SystemUI from 'expo-system-ui'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {KeyboardControllerProvider} from '#/lib/hooks/useEnableKeyboardController'
import {QueryProvider} from '#/lib/react-query'
import {
initialize,
@@ -24,6 +25,7 @@ import {s} from '#/lib/styles'
import {ThemeProvider} from '#/lib/ThemeContext'
import I18nProvider from '#/locale/i18nProvider'
import {logger} from '#/logger'
import {isIOS} from '#/platform/detection'
import {Provider as A11yProvider} from '#/state/a11y'
import {Provider as MutedThreadsProvider} from '#/state/cache/thread-mutes'
import {Provider as DialogStateProvider} from '#/state/dialogs'
@@ -66,11 +68,15 @@ import {NuxDialogs} from '#/components/dialogs/nuxs'
import {useStarterPackEntry} from '#/components/hooks/useStarterPackEntry'
import {Provider as IntentDialogProvider} from '#/components/intents/IntentDialogs'
import {Provider as PortalProvider} from '#/components/Portal'
import {AppProfiler} from '#/AppProfiler'
import {Splash} from '#/Splash'
import {BottomSheetProvider} from '../modules/bottom-sheet'
import {BackgroundNotificationPreferencesProvider} from '../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider'
SplashScreen.preventAutoHideAsync()
if (isIOS) {
SystemUI.setBackgroundColorAsync('black')
}
/**
* Begin geolocation ASAP
@@ -184,40 +190,42 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<GeolocationProvider>
<A11yProvider>
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
<SessionProvider>
<PrefsStateProvider>
<I18nProvider>
<ShellStateProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<BottomSheetProvider>
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<IntentDialogProvider>
<InnerApp />
</IntentDialogProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</ShellStateProvider>
</I18nProvider>
</PrefsStateProvider>
</SessionProvider>
</KeyboardProvider>
</A11yProvider>
</GeolocationProvider>
<AppProfiler>
<GeolocationProvider>
<A11yProvider>
<KeyboardControllerProvider>
<SessionProvider>
<PrefsStateProvider>
<I18nProvider>
<ShellStateProvider>
<InvitesStateProvider>
<ModalStateProvider>
<DialogStateProvider>
<LightboxStateProvider>
<PortalProvider>
<BottomSheetProvider>
<StarterPackProvider>
<SafeAreaProvider
initialMetrics={initialWindowMetrics}>
<IntentDialogProvider>
<InnerApp />
</IntentDialogProvider>
</SafeAreaProvider>
</StarterPackProvider>
</BottomSheetProvider>
</PortalProvider>
</LightboxStateProvider>
</DialogStateProvider>
</ModalStateProvider>
</InvitesStateProvider>
</ShellStateProvider>
</I18nProvider>
</PrefsStateProvider>
</SessionProvider>
</KeyboardControllerProvider>
</A11yProvider>
</GeolocationProvider>
</AppProfiler>
)
}
+46 -49
View File
@@ -3,7 +3,6 @@ import '#/view/icons'
import './style.css'
import React, {useEffect, useState} from 'react'
import {KeyboardProvider} from 'react-native-keyboard-controller'
import {RootSiblingParent} from 'react-native-root-siblings'
import {SafeAreaProvider} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
@@ -102,54 +101,52 @@ function InnerApp() {
if (!isReady || !hasCheckedReferrer) return null
return (
<KeyboardProvider enabled={false}>
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<RootSiblingParent>
<VideoVolumeProvider>
<ActiveVideoProvider>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<ComposerProvider>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<HiddenRepliesProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<SafeAreaProvider>
<ProgressGuideProvider>
<Shell />
<NuxDialogs />
</ProgressGuideProvider>
</SafeAreaProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</HiddenRepliesProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</ComposerProvider>
</QueryProvider>
<ToastContainer />
</React.Fragment>
</ActiveVideoProvider>
</VideoVolumeProvider>
</RootSiblingParent>
</ThemeProvider>
</Alf>
</KeyboardProvider>
<Alf theme={theme}>
<ThemeProvider theme={theme}>
<RootSiblingParent>
<VideoVolumeProvider>
<ActiveVideoProvider>
<React.Fragment
// Resets the entire tree below when it changes:
key={currentAccount?.did}>
<QueryProvider currentDid={currentAccount?.did}>
<ComposerProvider>
<StatsigProvider>
<MessagesProvider>
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
<LabelDefsProvider>
<ModerationOptsProvider>
<LoggedOutViewProvider>
<SelectedFeedProvider>
<HiddenRepliesProvider>
<UnreadNotifsProvider>
<BackgroundNotificationPreferencesProvider>
<MutedThreadsProvider>
<SafeAreaProvider>
<ProgressGuideProvider>
<Shell />
<NuxDialogs />
</ProgressGuideProvider>
</SafeAreaProvider>
</MutedThreadsProvider>
</BackgroundNotificationPreferencesProvider>
</UnreadNotifsProvider>
</HiddenRepliesProvider>
</SelectedFeedProvider>
</LoggedOutViewProvider>
</ModerationOptsProvider>
</LabelDefsProvider>
</MessagesProvider>
</StatsigProvider>
</ComposerProvider>
</QueryProvider>
<ToastContainer />
</React.Fragment>
</ActiveVideoProvider>
</VideoVolumeProvider>
</RootSiblingParent>
</ThemeProvider>
</Alf>
)
}
+29
View File
@@ -0,0 +1,29 @@
import React, {Profiler} from 'react'
// Don't let it get stripped out in profiling builds (which apply production Babel preset).
const log = (global as any)['con' + 'sole'].log
function onRender(id: string, phase: string, actualDuration: number) {
if (!__DEV__) {
// This block of code will exist in the production build of the app.
// However, only profiling builds of React call `onRender` so it's dead code in actual production.
const message = `<Profiler> ${id}:${phase} ${
actualDuration > 500
? '(╯°□°)╯ '
: actualDuration > 100
? '[!!] '
: actualDuration > 16
? '[!] '
: ''
}${Math.round(actualDuration)}ms`
log(message)
}
}
export function AppProfiler({children}: {children: React.ReactNode}) {
return (
<Profiler id="app" onRender={onRender}>
{children}
</Profiler>
)
}
+10 -7
View File
@@ -32,7 +32,7 @@ import {
import {RouteParams, State} from '#/lib/routes/types'
import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
import {bskyTitle} from '#/lib/strings/headings'
import {isAndroid, isNative, isWeb} from '#/platform/detection'
import {isNative, isWeb} from '#/platform/detection'
import {useModalControls} from '#/state/modals'
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useSession} from '#/state/session'
@@ -79,6 +79,7 @@ import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'
import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
import {AppIconSettingsScreen} from '#/screens/Settings/AppIconSettings'
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
import {
StarterPackScreen,
@@ -362,6 +363,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
requireAuth: true,
}}
/>
<Stack.Screen
name="AppIconSettings"
getComponent={() => AppIconSettingsScreen}
options={{
title: title(msg`App Icon`),
requireAuth: true,
}}
/>
<Stack.Screen
name="Hashtag"
getComponent={() => HashtagScreen}
@@ -453,7 +462,6 @@ function HomeTabNavigator() {
return (
<HomeTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
@@ -472,7 +480,6 @@ function SearchTabNavigator() {
return (
<SearchTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
@@ -490,7 +497,6 @@ function NotificationsTabNavigator() {
return (
<NotificationsTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
@@ -512,7 +518,6 @@ function MyProfileTabNavigator() {
return (
<MyProfileTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
@@ -538,7 +543,6 @@ function MessagesTabNavigator() {
return (
<MessagesTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
@@ -572,7 +576,6 @@ const FlatNavigator = () => {
<Flat.Navigator
screenListeners={screenListeners}
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
+7 -1
View File
@@ -1,7 +1,7 @@
import {Platform, StyleProp, StyleSheet, ViewStyle} from 'react-native'
import * as tokens from '#/alf/tokens'
import {native, web} from '#/alf/util/platform'
import {ios, native, web} from '#/alf/util/platform'
export const atoms = {
debug: {
@@ -312,6 +312,12 @@ export const atoms = {
border_r: {
borderRightWidth: StyleSheet.hairlineWidth,
},
curve_circular: ios({
borderCurve: 'circular',
}),
curve_continuous: ios({
borderCurve: 'continuous',
}),
/*
* Shadow
+1
View File
@@ -20,6 +20,7 @@ export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
export * from '#/alf/util/useGutterStyles'
export type Alf = {
themeName: ThemeName
+126
View File
@@ -0,0 +1,126 @@
import React, {Children} from 'react'
import {TextProps as RNTextProps} from 'react-native'
import {StyleProp, TextStyle} from 'react-native'
import {UITextView} from 'react-native-uitextview'
import createEmojiRegex from 'emoji-regex'
import {isNative} from '#/platform/detection'
import {isIOS} from '#/platform/detection'
import {Alf, applyFonts, atoms, flatten} from '#/alf'
/**
* Util to calculate lineHeight from a text size atom and a leading atom
*
* Example:
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
*/
export function leading<
Size extends {fontSize?: number},
Leading extends {lineHeight?: number},
>(textSize: Size, leading: Leading) {
const size = textSize?.fontSize || atoms.text_md.fontSize
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
return Math.round(size * lineHeight)
}
/**
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
* other relative leading atoms.
*
* If the `lineHeight` value is > 2, we assume it's an absolute value and
* returns it as-is.
*/
export function normalizeTextStyles(
styles: StyleProp<TextStyle>,
{
fontScale,
fontFamily,
}: {
fontScale: number
fontFamily: Alf['fonts']['family']
} & Pick<Alf, 'flags'>,
) {
const s = flatten(styles)
// should always be defined on these components
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
if (s?.lineHeight) {
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
s.lineHeight = Math.round(s.fontSize * s.lineHeight)
}
} else if (!isNative) {
s.lineHeight = s.fontSize
}
applyFonts(s, fontFamily)
return s
}
export type StringChild = string | (string | null)[]
export type TextProps = RNTextProps & {
/**
* Lets the user select text, to use the native copy and paste functionality.
*/
selectable?: boolean
/**
* Provides `data-*` attributes to the underlying `UITextView` component on
* web only.
*/
dataSet?: Record<string, string | number | undefined>
/**
* Appears as a small tooltip on web hover.
*/
title?: string
/**
* Whether the children could possibly contain emoji.
*/
emoji?: boolean
}
const EMOJI = createEmojiRegex()
export function childHasEmoji(children: React.ReactNode) {
let hasEmoji = false
Children.forEach(children, child => {
if (typeof child === 'string' && createEmojiRegex().test(child)) {
hasEmoji = true
}
})
return hasEmoji
}
export function renderChildrenWithEmoji(
children: React.ReactNode,
props: Omit<TextProps, 'children'> = {},
emoji: boolean,
) {
if (!isIOS || !emoji) {
return children
}
return Children.map(children, child => {
if (typeof child !== 'string') return child
const emojis = child.match(EMOJI)
if (emojis === null) {
return child
}
return child.split(EMOJI).map((stringPart, index) => [
stringPart,
emojis[index] ? (
<UITextView
{...props}
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
{emojis[index]}
</UITextView>
) : null,
])
})
}
const SINGLE_EMOJI_RE = /^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u
export function isOnlyEmoji(text: string) {
return text.length <= 15 && SINGLE_EMOJI_RE.test(text)
}
+18
View File
@@ -0,0 +1,18 @@
import * as NavigationBar from 'expo-navigation-bar'
import {isAndroid} from '#/platform/detection'
import {Theme} from '../types'
export function setNavigationBar(themeType: 'theme' | 'lightbox', t: Theme) {
if (isAndroid) {
if (themeType === 'theme') {
NavigationBar.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setBorderColorAsync(t.atoms.bg.backgroundColor)
NavigationBar.setButtonStyleAsync(t.name !== 'light' ? 'light' : 'dark')
} else {
NavigationBar.setBackgroundColorAsync('black')
NavigationBar.setBorderColorAsync('black')
NavigationBar.setButtonStyleAsync('light')
}
}
}
+21
View File
@@ -0,0 +1,21 @@
import React from 'react'
import {atoms as a, useBreakpoints, ViewStyleProp} from '#/alf'
export function useGutterStyles({
top,
bottom,
}: {
top?: boolean
bottom?: boolean
} = {}) {
const {gtMobile} = useBreakpoints()
return React.useMemo<ViewStyleProp['style']>(() => {
return [
a.px_lg,
top && a.pt_md,
bottom && a.pb_md,
gtMobile && [a.px_xl, top && a.pt_lg, bottom && a.pb_lg],
]
}, [gtMobile, top, bottom])
}
+1
View File
@@ -34,6 +34,7 @@ export function AppLanguageDropdown(_props: ViewStyleProp) {
return (
<View style={a.relative}>
<RNPickerSelect
darkTheme={t.scheme === 'dark'}
placeholder={{}}
value={sanitizedLang}
onValueChange={onChangeAppLanguage}
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {moderateProfile} from '@atproto/api'
-1
View File
@@ -458,7 +458,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
// @ts-ignore - this will always be a pressable
ref={ref}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
disabled={disabled || false}
accessibilityState={{
+12 -19
View File
@@ -11,7 +11,6 @@ import {
} from 'react-native'
import {
KeyboardAwareScrollView,
useKeyboardController,
useKeyboardHandler,
} from 'react-native-keyboard-controller'
import {runOnJS} from 'react-native-reanimated'
@@ -20,6 +19,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController'
import {ScrollProvider} from '#/lib/ScrollContext'
import {logger} from '#/logger'
import {isAndroid, isIOS} from '#/platform/detection'
@@ -75,7 +75,7 @@ export function Outer({
try {
cb()
} catch (e: any) {
logger.error('Error running close callback', e)
logger.error(e || 'Error running close callback')
}
}
@@ -199,27 +199,20 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
) {
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
const insets = useSafeAreaInsets()
const {setEnabled} = useKeyboardController()
useEnableKeyboardController(isIOS)
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
React.useEffect(() => {
if (!isIOS) {
return
}
setEnabled(true)
return () => {
setEnabled(false)
}
})
useKeyboardHandler({
onEnd: e => {
'worklet'
runOnJS(setKeyboardHeight)(e.height)
useKeyboardHandler(
{
onEnd: e => {
'worklet'
runOnJS(setKeyboardHeight)(e.height)
},
},
})
[],
)
const basePading =
(isIOS ? 30 : 50) + (isIOS ? keyboardHeight / 4 : keyboardHeight)
+51 -37
View File
@@ -7,12 +7,12 @@ import {
View,
ViewStyle,
} from 'react-native'
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {DismissableLayer} from '@radix-ui/react-dismissable-layer'
import {useFocusGuards} from '@radix-ui/react-focus-guards'
import {FocusScope} from '@radix-ui/react-focus-scope'
import {RemoveScroll} from 'react-remove-scroll'
import {logger} from '#/logger'
import {useDialogStateControlContext} from '#/state/dialogs'
@@ -42,7 +42,6 @@ export function Outer({
onClose,
}: React.PropsWithChildren<DialogOuterProps>) {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [isOpen, setIsOpen] = React.useState(false)
const {setDialogIsOpen} = useDialogStateControlContext()
@@ -105,43 +104,36 @@ export function Outer({
{isOpen && (
<Portal>
<Context.Provider value={context}>
<TouchableWithoutFeedback
accessibilityHint={undefined}
accessibilityLabel={_(msg`Close active dialog`)}
onPress={handleBackgroundPress}>
<View
style={[
web(a.fixed),
a.inset_0,
a.z_10,
a.align_center,
gtMobile ? a.p_lg : a.p_md,
{overflowY: 'auto'},
]}>
<Animated.View
entering={FadeIn.duration(150)}
// exiting={FadeOut.duration(150)}
<RemoveScroll>
<TouchableWithoutFeedback
accessibilityHint={undefined}
accessibilityLabel={_(msg`Close active dialog`)}
onPress={handleBackgroundPress}>
<View
style={[
web(a.fixed),
a.inset_0,
{opacity: 0.8, backgroundColor: t.palette.black},
]}
/>
<View
style={[
a.w_full,
a.z_20,
a.justify_center,
a.z_10,
a.align_center,
{
minHeight: web('calc(90vh - 36px)') || undefined,
},
gtMobile ? a.p_lg : a.p_md,
{overflowY: 'auto'},
]}>
{children}
<Backdrop />
<View
style={[
a.w_full,
a.z_20,
a.justify_center,
a.align_center,
{
minHeight: web('calc(90vh - 36px)') || undefined,
},
]}>
{children}
</View>
</View>
</View>
</TouchableWithoutFeedback>
</TouchableWithoutFeedback>
</RemoveScroll>
</Context.Provider>
</Portal>
)}
@@ -164,7 +156,7 @@ export function Inner({
useFocusGuards()
return (
<FocusScope loop asChild trapped>
<Animated.View
<View
role="dialog"
aria-role="dialog"
aria-label={label}
@@ -174,8 +166,6 @@ export function Inner({
onClick={stopPropagation}
onStartShouldSetResponder={_ => true}
onTouchEnd={stopPropagation}
entering={FadeInDown.duration(100)}
// exiting={FadeOut.duration(100)}
style={flatten([
a.relative,
a.rounded_md,
@@ -188,6 +178,8 @@ export function Inner({
shadowColor: t.palette.black,
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
shadowRadius: 30,
// @ts-ignore web only
animation: 'fadeIn ease-out 0.1s',
},
flatten(style),
])}>
@@ -201,7 +193,7 @@ export function Inner({
{children}
</View>
</DismissableLayer>
</Animated.View>
</View>
</FocusScope>
)
}
@@ -268,3 +260,25 @@ export function Close() {
export function Handle() {
return null
}
function Backdrop() {
const t = useTheme()
return (
<View
style={{
opacity: 0.8,
}}>
<View
style={[
a.fixed,
a.inset_0,
{
backgroundColor: t.palette.black,
// @ts-ignore web only
animation: 'fadeIn ease-out 0.15s',
},
]}
/>
</View>
)
}
+3 -5
View File
@@ -1,7 +1,7 @@
import React from 'react'
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'
import {atoms as a, useTheme, web} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography'
export function Header({
@@ -29,10 +29,8 @@ export function Header({
a.border_b,
t.atoms.border_contrast_medium,
t.atoms.bg,
web([
{borderRadiusTopLeft: a.rounded_md.borderRadius},
{borderRadiusTopRight: a.rounded_md.borderRadius},
]),
{borderTopLeftRadius: a.rounded_md.borderRadius},
{borderTopRightRadius: a.rounded_md.borderRadius},
style,
]}>
{renderLeft && (
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, flatten, useTheme, ViewStyleProp} from '#/alf'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {LinearGradient} from 'expo-linear-gradient'
import {atoms as a, tokens} from '#/alf'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {
-100
View File
@@ -1,100 +0,0 @@
import React, {useContext, useMemo} from 'react'
import {View, ViewStyle} from 'react-native'
import {StyleProp} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {ViewHeader} from '#/view/com/util/ViewHeader'
import {ScrollView} from '#/view/com/util/Views'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a} from '#/alf'
// Every screen should have a Layout component wrapping it.
// This component provides a default padding for the top of the screen.
// This allows certain screens to avoid the top padding if they want to.
const LayoutContext = React.createContext({
withinScreen: false,
topPaddingDisabled: false,
withinScrollView: false,
})
/**
* Every screen should have a Layout.Screen component wrapping it.
* This component provides a default padding for the top of the screen
* and height/minHeight
*/
let Screen = ({
disableTopPadding = false,
style,
...props
}: React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
}): React.ReactNode => {
const {top} = useSafeAreaInsets()
const context = useMemo(
() => ({
withinScreen: true,
topPaddingDisabled: disableTopPadding,
withinScrollView: false,
}),
[disableTopPadding],
)
return (
<LayoutContext.Provider value={context}>
<View
style={[
{paddingTop: disableTopPadding ? 0 : top},
a.util_screen_outer,
style,
]}
{...props}
/>
</LayoutContext.Provider>
)
}
Screen = React.memo(Screen)
export {Screen}
let Header = (
props: React.ComponentProps<typeof ViewHeader>,
): React.ReactNode => {
const {withinScrollView} = useContext(LayoutContext)
if (!withinScrollView) {
return (
<CenteredView topBorder={false} sideBorders>
<ViewHeader showOnDesktop showBorder {...props} />
</CenteredView>
)
} else {
return <ViewHeader showOnDesktop showBorder {...props} />
}
}
Header = React.memo(Header)
export {Header}
let Content = ({
style,
contentContainerStyle,
...props
}: React.ComponentProps<typeof ScrollView> & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}): React.ReactNode => {
const context = useContext(LayoutContext)
const newContext = useMemo(
() => ({...context, withinScrollView: true}),
[context],
)
return (
<LayoutContext.Provider value={newContext}>
<ScrollView
style={[a.flex_1, style]}
contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]}
{...props}
/>
</LayoutContext.Provider>
)
}
Content = React.memo(Content)
export {Content}
+167
View File
@@ -0,0 +1,167 @@
import React from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types'
import {isIOS} from '#/platform/detection'
import {useSetDrawerOpen} from '#/state/shell'
import {
atoms as a,
TextStyleProp,
useBreakpoints,
useGutterStyles,
useTheme,
web,
} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {Text} from '#/components/Typography'
const BUTTON_VISUAL_ALIGNMENT_OFFSET = 3
const BUTTON_SIZE = 34 // small button
export function Outer({children}: {children: React.ReactNode}) {
const t = useTheme()
const gutter = useGutterStyles()
const {gtMobile} = useBreakpoints()
return (
<View
style={[
a.w_full,
a.border_b,
a.flex_row,
a.align_center,
a.gap_sm,
gutter,
a.py_sm,
t.atoms.border_contrast_low,
gtMobile && [a.mx_auto, {maxWidth: 600}],
web({
transform: [
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}>
{children}
</View>
)
}
export function Content({children}: {children: React.ReactNode}) {
return (
<View style={[a.flex_1, a.justify_center, isIOS && a.align_center]}>
{children}
</View>
)
}
export function Slot({children}: {children?: React.ReactNode}) {
return (
<View
style={[
a.z_50,
{
width: BUTTON_SIZE,
},
]}>
{children}
</View>
)
}
export function BackButton() {
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
} else {
navigation.navigate('Home')
}
}, [navigation])
return (
<Slot>
<Button
label={_(msg`Go back`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
onPress={onPressBack}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
<ButtonIcon icon={ArrowLeft} size="lg" />
</Button>
</Slot>
)
}
export function MenuButton() {
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const {gtMobile} = useBreakpoints()
const onPress = React.useCallback(() => {
setDrawerOpen(true)
}, [setDrawerOpen])
return gtMobile ? null : (
<Slot>
<Button
label={_(msg`Open drawer menu`)}
size="small"
variant="ghost"
color="secondary"
shape="round"
onPress={onPress}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
<ButtonIcon icon={Menu} size="lg" />
</Button>
</Slot>
)
}
export function TitleText({
children,
style,
}: {children: React.ReactNode} & TextStyleProp) {
const {gtMobile} = useBreakpoints()
return (
<Text
style={[
a.text_lg,
a.font_heavy,
a.leading_tight,
isIOS && a.text_center,
gtMobile && [a.text_xl],
style,
]}
numberOfLines={2}>
{children}
</Text>
)
}
export function SubtitleText({children}: {children: React.ReactNode}) {
const t = useTheme()
return (
<Text
style={[
a.text_sm,
a.leading_snug,
isIOS && a.text_center,
t.atoms.text_contrast_medium,
]}
numberOfLines={2}>
{children}
</Text>
)
}
+194
View File
@@ -0,0 +1,194 @@
import React, {useContext, useMemo} from 'react'
import {StyleSheet, View, ViewProps, ViewStyle} from 'react-native'
import {StyleProp} from 'react-native'
import {
KeyboardAwareScrollView,
KeyboardAwareScrollViewProps,
} from 'react-native-keyboard-controller'
import Animated, {AnimatedScrollViewProps} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {isWeb} from '#/platform/detection'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
export * as Header from '#/components/Layout/Header'
// Every screen should have a Layout component wrapping it.
// This component provides a default padding for the top of the screen.
// This allows certain screens to avoid the top padding if they want to.
const LayoutContext = React.createContext({
withinScreen: false,
topPaddingDisabled: false,
withinScrollView: false,
})
export type ScreenProps = React.ComponentProps<typeof View> & {
disableTopPadding?: boolean
style?: StyleProp<ViewStyle>
temp__enableWebBorders?: boolean
}
/**
* Every screen should have a Layout.Screen component wrapping it.
* This component provides a default padding for the top of the screen
* and height/minHeight
*/
export const Screen = React.memo(function Screen({
disableTopPadding = false,
style,
temp__enableWebBorders,
...props
}: ScreenProps) {
const {top} = useSafeAreaInsets()
const context = useMemo(
() => ({
withinScreen: true,
topPaddingDisabled: disableTopPadding,
withinScrollView: false,
}),
[disableTopPadding],
)
return (
<LayoutContext.Provider value={context}>
{isWeb && temp__enableWebBorders && <WebCenterBorders />}
<View
style={[
{paddingTop: disableTopPadding ? 0 : top},
a.util_screen_outer,
style,
]}
{...props}
/>
</LayoutContext.Provider>
)
})
export type ContentProps = AnimatedScrollViewProps & {
style?: StyleProp<ViewStyle>
contentContainerStyle?: StyleProp<ViewStyle>
}
export const Content = React.memo(function Content({
children,
style,
contentContainerStyle,
...props
}: ContentProps) {
const context = useContext(LayoutContext)
const newContext = useMemo(
() => ({...context, withinScrollView: true}),
[context],
)
return (
<LayoutContext.Provider value={newContext}>
<Animated.ScrollView
id="content"
style={[styles.scrollViewCommonStyles, style]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
{...props}>
{isWeb ? (
// @ts-ignore web only -esb
<Center>{children}</Center>
) : (
children
)}
</Animated.ScrollView>
</LayoutContext.Provider>
)
})
export type KeyboardAwareContentProps = KeyboardAwareScrollViewProps & {
children: React.ReactNode
contentContainerStyle?: StyleProp<ViewStyle>
}
export const KeyboardAwareContent = React.forwardRef(function LayoutScrollView(
{children, style, contentContainerStyle, ...props}: KeyboardAwareContentProps,
_ref: React.Ref<typeof KeyboardAwareScrollView>,
) {
return (
<KeyboardAwareScrollView
style={[styles.scrollViewCommonStyles, style]}
contentContainerStyle={[
styles.scrollViewContentContainer,
contentContainerStyle,
]}
keyboardShouldPersistTaps="handled"
{...props}>
{isWeb ? <Center>{children}</Center> : children}
</KeyboardAwareScrollView>
)
})
export const WebCenterBorders = React.forwardRef(function LayoutContent() {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return gtMobile ? (
<View
style={[
a.fixed,
a.inset_0,
a.border_l,
a.border_r,
t.atoms.border_contrast_low,
web({
width: 602,
left: '50%',
transform: [
{
translateX: '-50%',
},
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}
/>
) : null
})
export const Center = React.forwardRef(function LayoutContent(
{children, style, ...props}: ViewProps,
ref: React.Ref<View>,
) {
const {gtMobile} = useBreakpoints()
return (
<View
ref={ref}
style={[
a.util_screen_outer,
a.w_full,
a.mx_auto,
gtMobile && {
maxWidth: 600,
},
style,
web({
transform: [
{
translateX:
'calc(-1 * var(--removed-body-scroll-bar-size, 0px) / 2)',
},
],
}),
]}
{...props}>
{children}
</View>
)
})
const styles = StyleSheet.create({
scrollViewCommonStyles: {
width: '100%',
},
scrollViewContentContainer: {
paddingBottom: 100,
},
})
+1 -1
View File
@@ -1,4 +1,4 @@
import React, {useCallback, useMemo} from 'react'
import {useCallback, useMemo} from 'react'
import {ActivityIndicator, FlatList, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
+7 -11
View File
@@ -274,11 +274,6 @@ export function InlineLinkText({
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
const flattenedStyle = flatten(style) || {}
return (
@@ -289,19 +284,20 @@ export function InlineLinkText({
{...rest}
style={[
{color: t.palette.primary_500},
(hovered || focused || pressed) &&
(hovered || focused) &&
!disableUnderline && {
...web({outline: 0}),
textDecorationLine: 'underline',
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
...web({
outline: 0,
textDecorationLine: 'underline',
textDecorationColor:
flattenedStyle.color ?? t.palette.primary_500,
}),
},
flattenedStyle,
]}
role="link"
onPress={download ? undefined : onPress}
onLongPress={onLongPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={onHoverIn}
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, flatten, useTheme} from '#/alf'
-2
View File
@@ -1,5 +1,3 @@
/* eslint-disable react/prop-types */
import React from 'react'
import {Pressable, StyleProp, View, ViewStyle} from 'react-native'
import {msg} from '@lingui/macro'
@@ -302,8 +302,8 @@ export function ProfileHoverCardInner(props: ProfileHoverCardProps) {
const animationStyle = {
animation:
currentState.stage === 'hiding'
? `avatarHoverFadeOut ${HIDE_DURATION}ms both`
: `avatarHoverFadeIn ${SHOW_DURATION}ms both`,
? `fadeOut ${HIDE_DURATION}ms both`
: `fadeIn ${SHOW_DURATION}ms both`,
}
return (
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import * as Progress from 'react-native-progress'
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyLabelerDefs} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
+9 -24
View File
@@ -9,6 +9,7 @@ import {NavigationProp} from '#/lib/routes/types'
import {toShortUrl} from '#/lib/strings/url-helpers'
import {isNative} from '#/platform/detection'
import {atoms as a, flatten, native, TextStyleProp, useTheme, web} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {InlineLinkText, LinkProps} from '#/components/Link'
import {ProfileHoverCard} from '#/components/ProfileHoverCard'
@@ -53,7 +54,6 @@ export function RichText({
const plainStyles = [a.leading_snug, flattenedStyle]
const interactiveStyles = [
a.leading_snug,
a.pointer_events_auto,
flatten(interactiveStyle),
flattenedStyle,
]
@@ -151,17 +151,14 @@ export function RichText({
/>,
)
} else {
els.push(
<Text key={key} emoji style={plainStyles}>
{segment.text}
</Text>,
)
els.push(segment.text)
}
key++
}
return (
<Text
emoji
selectable={selectable}
testID={testID}
style={plainStyles}
@@ -194,11 +191,6 @@ function RichTextTag({
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
const navigation = useNavigation<NavigationProp>()
const navigateToPage = React.useCallback(() => {
@@ -228,8 +220,6 @@ function RichTextTag({
accessibilityRole: isNative ? 'button' : undefined,
onPress: navigateToPage,
onLongPress: openDialog,
onPressIn: onPressIn,
onPressOut: onPressOut,
})}
{...web({
onMouseEnter: onHoverIn,
@@ -243,10 +233,12 @@ function RichTextTag({
cursor: 'pointer',
}),
{color: t.palette.primary_500},
(hovered || focused || pressed) && {
...web({outline: 0}),
textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500,
(hovered || focused) && {
...web({
outline: 0,
textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500,
}),
},
style,
]}>
@@ -256,10 +248,3 @@ function RichTextTag({
</React.Fragment>
)
}
export function isOnlyEmoji(text: string) {
return (
text.length <= 15 &&
/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u.test(text)
)
}
@@ -40,7 +40,7 @@ export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
ref,
) {
const t = useTheme()
const bottomBarOffset = useBottomBarOffset(300)
const bottomBarOffset = useBottomBarOffset(headerHeight)
const initialNumToRender = useInitialNumToRender()
const {currentAccount} = useSession()
const {data, refetch, isError} = useAllListMembersQuery(listUri)
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker'
@@ -1,4 +1,4 @@
import React, {useRef} from 'react'
import {useRef} from 'react'
import type {ListRenderItemInfo} from 'react-native'
import {View} from 'react-native'
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
@@ -1,4 +1,3 @@
import React from 'react'
import {Keyboard, View} from 'react-native'
import {
AppBskyActorDefs,
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {isTouchDevice} from '#/lib/browser'
+164 -150
View File
@@ -40,9 +40,37 @@ export function TagMenu({
tag: string
authorHandle?: string
}>) {
const navigation = useNavigation<NavigationProp>()
return (
<>
{children}
<Dialog.Outer control={control}>
<Dialog.Handle />
<TagMenuInner
control={control}
tag={tag}
authorHandle={authorHandle}
navigation={navigation}
/>
</Dialog.Outer>
</>
)
}
function TagMenuInner({
control,
tag,
authorHandle,
navigation,
}: {
control: Dialog.DialogOuterProps['control']
tag: string
authorHandle?: string
// Passed down because on native, we don't use real portals (and context would be wrong).
navigation: NavigationProp
}) {
const {_} = useLingui()
const t = useTheme()
const navigation = useNavigation<NavigationProp>()
const {isLoading: isPreferencesLoading, data: preferences} =
usePreferencesQuery()
const {
@@ -79,32 +107,75 @@ export function TagMenu({
}, [tag, preferences?.moderationPrefs?.mutedWords])
return (
<>
{children}
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
{isPreferencesLoading ? (
<View style={[a.w_full, a.align_center]}>
<Loader size="lg" />
</View>
) : (
<>
<Dialog.Inner label={_(msg`Tag menu: ${displayTag}`)}>
{isPreferencesLoading ? (
<View style={[a.w_full, a.align_center]}>
<Loader size="lg" />
</View>
) : (
<>
<View
style={[
a.rounded_md,
a.border,
a.mb_md,
t.atoms.border_contrast_low,
t.atoms.bg_contrast_25,
]}>
<Link
label={_(msg`View all posts with tag ${displayTag}`)}
{...createStaticClick(() => {
control.close(() => {
navigation.push('Hashtag', {
tag: encodeURIComponent(tag),
})
})
})}>
<View
style={[
a.rounded_md,
a.border,
a.mb_md,
t.atoms.border_contrast_low,
t.atoms.bg_contrast_25,
a.w_full,
a.flex_row,
a.align_center,
a.justify_start,
a.gap_md,
a.px_lg,
a.py_md,
]}>
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
<Text
numberOfLines={1}
ellipsizeMode="middle"
style={[
a.flex_1,
a.text_md,
a.font_bold,
native({top: 2}),
t.atoms.text_contrast_medium,
]}>
<Trans>
See{' '}
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
{displayTag}
</Text>{' '}
posts
</Trans>
</Text>
</View>
</Link>
{authorHandle && !isInvalidHandle(authorHandle) && (
<>
<Divider />
<Link
label={_(msg`View all posts with tag ${displayTag}`)}
label={_(
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
)}
{...createStaticClick(() => {
control.close(() => {
navigation.push('Hashtag', {
tag: encodeURIComponent(tag),
author: authorHandle,
})
})
})}>
@@ -118,7 +189,7 @@ export function TagMenu({
a.px_lg,
a.py_md,
]}>
<Search size="lg" style={[t.atoms.text_contrast_medium]} />
<Person size="lg" style={[t.atoms.text_contrast_medium]} />
<Text
numberOfLines={1}
ellipsizeMode="middle"
@@ -134,143 +205,86 @@ export function TagMenu({
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
{displayTag}
</Text>{' '}
posts
posts by this user
</Trans>
</Text>
</View>
</Link>
</>
)}
{authorHandle && !isInvalidHandle(authorHandle) && (
<>
<Divider />
{preferences ? (
<>
<Divider />
<Link
label={_(
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
)}
{...createStaticClick(() => {
control.close(() => {
navigation.push('Hashtag', {
tag: encodeURIComponent(tag),
author: authorHandle,
})
})
})}>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_start,
a.gap_md,
a.px_lg,
a.py_md,
]}>
<Person
size="lg"
style={[t.atoms.text_contrast_medium]}
/>
<Text
numberOfLines={1}
ellipsizeMode="middle"
style={[
a.flex_1,
a.text_md,
a.font_bold,
native({top: 2}),
t.atoms.text_contrast_medium,
]}>
<Trans>
See{' '}
<Text
style={[a.text_md, a.font_bold, t.atoms.text]}>
{displayTag}
</Text>{' '}
posts by this user
</Trans>
</Text>
</View>
</Link>
</>
)}
{preferences ? (
<>
<Divider />
<Button
label={
isMuted
? _(msg`Unmute all ${displayTag} posts`)
: _(msg`Mute all ${displayTag} posts`)
<Button
label={
isMuted
? _(msg`Unmute all ${displayTag} posts`)
: _(msg`Mute all ${displayTag} posts`)
}
onPress={() => {
control.close(() => {
if (isMuted) {
resetUpsert()
removeMutedWords(removeableMuteWords)
} else {
resetRemove()
upsertMutedWord([
{
value: tag,
targets: ['tag'],
actorTarget: 'all',
},
])
}
onPress={() => {
control.close(() => {
if (isMuted) {
resetUpsert()
removeMutedWords(removeableMuteWords)
} else {
resetRemove()
upsertMutedWord([
{
value: tag,
targets: ['tag'],
actorTarget: 'all',
},
])
}
})
}}>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_start,
a.gap_md,
a.px_lg,
a.py_md,
]}>
<Mute
size="lg"
style={[t.atoms.text_contrast_medium]}
/>
<Text
numberOfLines={1}
ellipsizeMode="middle"
style={[
a.flex_1,
a.text_md,
a.font_bold,
native({top: 2}),
t.atoms.text_contrast_medium,
]}>
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
{displayTag}
</Text>{' '}
<Trans>posts</Trans>
</Text>
</View>
</Button>
</>
) : null}
</View>
})
}}>
<View
style={[
a.w_full,
a.flex_row,
a.align_center,
a.justify_start,
a.gap_md,
a.px_lg,
a.py_md,
]}>
<Mute size="lg" style={[t.atoms.text_contrast_medium]} />
<Text
numberOfLines={1}
ellipsizeMode="middle"
style={[
a.flex_1,
a.text_md,
a.font_bold,
native({top: 2}),
t.atoms.text_contrast_medium,
]}>
{isMuted ? _(msg`Unmute`) : _(msg`Mute`)}{' '}
<Text style={[a.text_md, a.font_bold, t.atoms.text]}>
{displayTag}
</Text>{' '}
<Trans>posts</Trans>
</Text>
</View>
</Button>
</>
) : null}
</View>
<Button
label={_(msg`Close this dialog`)}
size="small"
variant="ghost"
color="secondary"
onPress={() => control.close()}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
</>
)}
</Dialog.Inner>
</Dialog.Outer>
</>
<Button
label={_(msg`Close this dialog`)}
size="small"
variant="ghost"
color="secondary"
onPress={() => control.close()}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
</>
)}
</Dialog.Inner>
)
}
+10 -139
View File
@@ -1,140 +1,15 @@
import React from 'react'
import {StyleProp, TextProps as RNTextProps, TextStyle} from 'react-native'
import {UITextView} from 'react-native-uitextview'
import createEmojiRegex from 'emoji-regex'
import {logger} from '#/logger'
import {isIOS, isNative} from '#/platform/detection'
import {Alf, applyFonts, atoms, flatten, useAlf, useTheme, web} from '#/alf'
import {atoms, flatten, useAlf, useTheme, web} from '#/alf'
import {
childHasEmoji,
normalizeTextStyles,
renderChildrenWithEmoji,
TextProps,
} from '#/alf/typography'
import {IS_DEV} from '#/env'
export type StringChild = string | (string | null)[]
export type TextProps = Omit<RNTextProps, 'children'> & {
/**
* Lets the user select text, to use the native copy and paste functionality.
*/
selectable?: boolean
/**
* Provides `data-*` attributes to the underlying `UITextView` component on
* web only.
*/
dataSet?: Record<string, string | number | undefined>
/**
* Appears as a small tooltip on web hover.
*/
title?: string
} & (
| {
emoji?: true
children: StringChild
}
| {
emoji?: false
children: RNTextProps['children']
}
)
const EMOJI = createEmojiRegex()
export function childHasEmoji(children: React.ReactNode) {
return (Array.isArray(children) ? children : [children]).some(
child => typeof child === 'string' && createEmojiRegex().test(child),
)
}
export function childIsString(
children: React.ReactNode,
): children is StringChild {
return (
typeof children === 'string' ||
(Array.isArray(children) &&
children.every(child => typeof child === 'string' || child === null))
)
}
export function renderChildrenWithEmoji(
children: StringChild,
props: Omit<TextProps, 'children'> = {},
) {
const normalized = Array.isArray(children) ? children : [children]
return (
<UITextView {...props}>
{normalized.map(child => {
if (typeof child !== 'string') return child
const emojis = child.match(EMOJI)
if (emojis === null) {
return child
}
return child.split(EMOJI).map((stringPart, index) => (
<UITextView key={index} {...props}>
{stringPart}
{emojis[index] ? (
<UITextView
{...props}
style={[props?.style, {color: 'black', fontFamily: 'System'}]}>
{emojis[index]}
</UITextView>
) : null}
</UITextView>
))
})}
</UITextView>
)
}
/**
* Util to calculate lineHeight from a text size atom and a leading atom
*
* Example:
* `leading(atoms.text_md, atoms.leading_normal)` // => 24
*/
export function leading<
Size extends {fontSize?: number},
Leading extends {lineHeight?: number},
>(textSize: Size, leading: Leading) {
const size = textSize?.fontSize || atoms.text_md.fontSize
const lineHeight = leading?.lineHeight || atoms.leading_normal.lineHeight
return Math.round(size * lineHeight)
}
/**
* Ensures that `lineHeight` defaults to a relative value of `1`, or applies
* other relative leading atoms.
*
* If the `lineHeight` value is > 2, we assume it's an absolute value and
* returns it as-is.
*/
export function normalizeTextStyles(
styles: StyleProp<TextStyle>,
{
fontScale,
fontFamily,
}: {
fontScale: number
fontFamily: Alf['fonts']['family']
} & Pick<Alf, 'flags'>,
) {
const s = flatten(styles)
// should always be defined on these components
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
if (s?.lineHeight) {
if (s.lineHeight !== 0 && s.lineHeight <= 2) {
s.lineHeight = Math.round(s.fontSize * s.lineHeight)
}
} else if (!isNative) {
s.lineHeight = s.fontSize
}
applyFonts(s, fontFamily)
return s
}
export type {TextProps}
/**
* Our main text component. Use this most of the time.
@@ -162,10 +37,6 @@ export function Text({
`Text: emoji detected but emoji not enabled: "${children}"\n\nPlease add <Text emoji />'`,
)
}
if (emoji && !childIsString(children)) {
logger.error('Text: when <Text emoji />, children can only be strings.')
}
}
const shared = {
@@ -178,12 +49,12 @@ export function Text({
return (
<UITextView {...shared}>
{isIOS && emoji ? renderChildrenWithEmoji(children, shared) : children}
{renderChildrenWithEmoji(children, shared, emoji ?? false)}
</UITextView>
)
}
export function createHeadingElement({level}: {level: number}) {
function createHeadingElement({level}: {level: number}) {
return function HeadingElement({style, ...rest}: TextProps) {
const attr =
web({
+1 -1
View File
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import {useCallback} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -256,6 +256,9 @@ export function PostInteractionSettingsForm({
} else {
newSelected.splice(i, 1)
}
if (newSelected.length === 0) {
newSelected.push({type: 'everybody'})
}
onChangeThreadgateAllowUISettings(newSelected)
}
@@ -306,13 +309,9 @@ export function PostInteractionSettingsForm({
}
value={quotesEnabled}
onChange={onChangeQuotesEnabled}
style={[, a.justify_between, a.pt_xs]}>
style={[a.justify_between, a.pt_xs]}>
<Text style={[t.atoms.text_contrast_medium]}>
{quotesEnabled ? (
<Trans>Quote posts enabled</Trans>
) : (
<Trans>Quote posts disabled</Trans>
)}
<Trans>Quote posts enabled</Trans>
</Text>
<Toggle.Switch />
</Toggle.Item>
@@ -483,7 +482,7 @@ function Selectable({
a.justify_between,
a.rounded_sm,
a.p_md,
{height: 40}, // for consistency with checkmark icon visible or not
{minHeight: 40}, // for consistency with checkmark icon visible or not
t.atoms.bg_contrast_50,
(hovered || focused) && t.atoms.bg_contrast_100,
isSelected && {
+1 -1
View File
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import {useCallback} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+1 -1
View File
@@ -115,7 +115,7 @@ let ConvoMenu = ({
{...props}
onPress={() => {
Keyboard.dismiss()
// eslint-disable-next-line react/prop-types -- eslint is confused by the name `props`
props.onPress()
}}
style={[
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
+2 -1
View File
@@ -19,10 +19,11 @@ import {ConvoItem} from '#/state/messages/convo/types'
import {useSession} from '#/state/session'
import {TimeElapsed} from '#/view/com/util/TimeElapsed'
import {atoms as a, useTheme} from '#/alf'
import {isOnlyEmoji} from '#/alf/typography'
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
import {isOnlyEmoji, RichText} from '../RichText'
import {RichText} from '../RichText'
import {DateDivider} from './DateDivider'
import {MessageItemEmbed} from './MessageItemEmbed'
import {localDateString} from './util'
@@ -1,4 +1,3 @@
import React from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+1 -1
View File
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import {useCallback} from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -1,4 +1,4 @@
import React, {useCallback} from 'react'
import {useCallback} from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -1,4 +1,3 @@
import React from 'react'
import {Pressable, View} from 'react-native'
import {useLingui} from '@lingui/react'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
+10
View File
@@ -16,17 +16,21 @@ import {
es,
fi,
fr,
gl,
hi,
hu,
id,
it,
ja,
ko,
nl,
pl,
ptBR,
ru,
th,
tr,
uk,
vi,
zhCN,
zhHK,
zhTW,
@@ -40,6 +44,8 @@ import {useLanguagePrefs} from '#/state/preferences'
*/
const locales: Record<AppLanguage, Locale | undefined> = {
en: undefined,
ast: undefined,
an: undefined,
ca,
de,
['en-GB']: enGB,
@@ -47,17 +53,21 @@ const locales: Record<AppLanguage, Locale | undefined> = {
fi,
fr,
ga: undefined,
gl,
hi,
hu,
id,
it,
ja,
ko,
nl,
pl,
['pt-BR']: ptBR,
ru,
th,
tr,
uk,
vi,
['zh-CN']: zhCN,
['zh-HK']: zhHK,
['zh-TW']: zhTW,
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleSheet, TextProps} from 'react-native'
import type {PathProps, SvgProps} from 'react-native-svg'
import {Defs, LinearGradient, Stop} from 'react-native-svg'
+31 -9
View File
@@ -31,6 +31,37 @@ export function ContentHider({
ignoreMute?: boolean
style?: StyleProp<ViewStyle>
childContainerStyle?: StyleProp<ViewStyle>
}>) {
const blur = modui?.blurs[0]
if (!blur || (ignoreMute && isJustAMute(modui))) {
return (
<View testID={testID} style={style}>
{children}
</View>
)
}
return (
<ContentHiderActive
testID={testID}
modui={modui}
style={style}
childContainerStyle={childContainerStyle}>
{children}
</ContentHiderActive>
)
}
function ContentHiderActive({
testID,
modui,
style,
childContainerStyle,
children,
}: React.PropsWithChildren<{
testID?: string
modui: ModerationUI
style?: StyleProp<ViewStyle>
childContainerStyle?: StyleProp<ViewStyle>
}>) {
const t = useTheme()
const {_} = useLingui()
@@ -40,7 +71,6 @@ export function ContentHider({
const {labelDefs} = useLabelDefinitions()
const globalLabelStrings = useGlobalLabelStrings()
const {i18n} = useLingui()
const blur = modui?.blurs[0]
const desc = useModerationCauseDescription(blur)
@@ -99,14 +129,6 @@ export function ContentHider({
globalLabelStrings,
])
if (!blur || (ignoreMute && isJustAMute(modui))) {
return (
<View testID={testID} style={style}>
{children}
</View>
)
}
return (
<View testID={testID} style={[a.overflow_hidden, style]}>
<ModerationDetailsDialog control={control} modcause={blur} />
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api'
import {msg, Plural} from '@lingui/macro'
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {ModerationCause} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleProp, ViewStyle} from 'react-native'
import {ModerationCause, ModerationUI} from '@atproto/api'
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleProp, ViewStyle} from 'react-native'
import {ModerationDecision} from '@atproto/api'
+10 -27
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import {atoms as a, useTheme} from '#/alf'
@@ -10,39 +9,23 @@ export function PlayButtonIcon({size = 32}: {size?: number}) {
const fg = t.name === 'light' ? t.palette.contrast_975 : t.palette.contrast_25
return (
<View
style={[
a.rounded_full,
a.overflow_hidden,
a.align_center,
a.justify_center,
t.atoms.shadow_lg,
{
width: size + size / 1.5,
height: size + size / 1.5,
},
]}>
<>
<View
style={[
a.absolute,
a.inset_0,
a.rounded_full,
{
backgroundColor: bg,
shadowColor: 'black',
shadowRadius: 32,
shadowOpacity: 0.5,
elevation: 24,
width: size + size / 1.5,
height: size + size / 1.5,
opacity: 0.7,
},
]}
/>
<PlayIcon
width={size}
fill={fg}
style={[
a.relative,
a.z_10,
{
left: size / 50,
},
]}
/>
</View>
<PlayIcon width={size} fill={fg} style={a.absolute} />
</>
)
}
+3 -1
View File
@@ -21,7 +21,7 @@ export const STARTER_PACK_MAX_SIZE = 150
// code and update this number with each release until we can get the
// server route done.
// -prf
export const JOINED_THIS_WEEK = 3500000 // estimate as of 11/16/24
export const JOINED_THIS_WEEK = 2880000 // estimate as of 11/26/24
export const DISCOVER_DEBUG_DIDS: Record<string, true> = {
'did:plc:oisofpd7lj26yvgiivf3lxsi': true, // hailey.at
@@ -29,6 +29,7 @@ export const DISCOVER_DEBUG_DIDS: Record<string, true> = {
'did:plc:p2cp5gopk7mgjegy6wadk3ep': true, // samuel.bsky.team
'did:plc:ragtjsm2j2vknwkz3zp4oxrd': true, // pfrazee.com
'did:plc:vpkhqolt662uhesyj6nxm7ys': true, // why.bsky.team
'did:plc:3jpt2mvvsumj2r7eqk4gzzjz': true, // esb.lol
}
const BASE_FEEDBACK_FORM_URL = `${HELP_DESK_URL}/requests/new`
@@ -154,6 +155,7 @@ export const SUPPORTED_MIME_TYPES = [
'video/mpeg',
'video/webm',
'video/quicktime',
'image/gif',
] as const
export type SupportedMimeTypes = (typeof SUPPORTED_MIME_TYPES)[number]
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {View} from 'react-native'
import Animated, {
Keyframe,
@@ -1,4 +1,3 @@
import React from 'react'
import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native'
import Animated, {
cancelAnimation,
@@ -0,0 +1,110 @@
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
} from 'react'
import {
KeyboardProvider,
useKeyboardController,
} from 'react-native-keyboard-controller'
import {useFocusEffect} from '@react-navigation/native'
import {IS_DEV} from '#/env'
const KeyboardControllerRefCountContext = createContext<{
incrementRefCount: () => void
decrementRefCount: () => void
}>({
incrementRefCount: () => {},
decrementRefCount: () => {},
})
export function KeyboardControllerProvider({
children,
}: {
children: React.ReactNode
}) {
return (
<KeyboardProvider
enabled={false}
// I don't think this is necessary, but Chesterton's fence and all that -sfn
statusBarTranslucent={true}>
<KeyboardControllerProviderInner>
{children}
</KeyboardControllerProviderInner>
</KeyboardProvider>
)
}
function KeyboardControllerProviderInner({
children,
}: {
children: React.ReactNode
}) {
const {setEnabled} = useKeyboardController()
const refCount = useRef(0)
const value = useMemo(
() => ({
incrementRefCount: () => {
refCount.current++
setEnabled(refCount.current > 0)
},
decrementRefCount: () => {
refCount.current--
setEnabled(refCount.current > 0)
if (IS_DEV && refCount.current < 0) {
console.error('KeyboardController ref count < 0')
}
},
}),
[setEnabled],
)
return (
<KeyboardControllerRefCountContext.Provider value={value}>
{children}
</KeyboardControllerRefCountContext.Provider>
)
}
export function useEnableKeyboardController(shouldEnable: boolean) {
const {incrementRefCount, decrementRefCount} = useContext(
KeyboardControllerRefCountContext,
)
useEffect(() => {
if (!shouldEnable) {
return
}
incrementRefCount()
return () => {
decrementRefCount()
}
}, [shouldEnable, incrementRefCount, decrementRefCount])
}
/**
* Like `useEnableKeyboardController`, but using `useFocusEffect`
*/
export function useEnableKeyboardControllerScreen(shouldEnable: boolean) {
const {incrementRefCount, decrementRefCount} = useContext(
KeyboardControllerRefCountContext,
)
useFocusEffect(
useCallback(() => {
if (!shouldEnable) {
return
}
incrementRefCount()
return () => {
decrementRefCount()
}
}, [shouldEnable, incrementRefCount, decrementRefCount]),
)
}
+39
View File
@@ -0,0 +1,39 @@
import {useState} from 'react'
import {AnimatedRef, measure, MeasuredDimensions} from 'react-native-reanimated'
export type HandleRef = {
(node: any): void
current: null | number
}
// This is a lighterweight alternative to `useAnimatedRef()` for imperative UI thread actions.
// Render it like <View ref={ref} />, then pass `ref.current` to `measureHandle()` and such.
export function useHandleRef(): HandleRef {
return useState(() => {
const ref = (node: any) => {
if (node) {
ref.current =
node._nativeTag ??
node.__nativeTag ??
node.canonical?.nativeTag ??
null
} else {
ref.current = null
}
}
ref.current = null
return ref
})[0] as HandleRef
}
// When using this version, you need to read ref.current on the JS thread, and pass it to UI.
export function measureHandle(
current: number | null,
): MeasuredDimensions | null {
'worklet'
if (current !== null) {
return measure((() => current) as AnimatedRef<any>)
} else {
return null
}
}
-31
View File
@@ -1,31 +0,0 @@
import {useEffect} from 'react'
import {isWeb} from '#/platform/detection'
let refCount = 0
function incrementRefCount() {
if (refCount === 0) {
document.body.style.overflow = 'hidden'
document.documentElement.style.scrollbarGutter = 'auto'
}
refCount++
}
function decrementRefCount() {
refCount--
if (refCount === 0) {
document.body.style.overflow = ''
document.documentElement.style.scrollbarGutter = ''
}
}
export function useWebBodyScrollLock(isLockActive: boolean) {
useEffect(() => {
if (!isWeb || !isLockActive) {
return
}
incrementRefCount()
return () => decrementRefCount()
})
}
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {StyleProp, TextStyle, ViewStyle} from 'react-native'
import Svg, {Ellipse, Line, Path, Rect} from 'react-native-svg'
+4
View File
@@ -32,6 +32,8 @@ export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) {
return 'mpeg'
case 'video/quicktime':
return 'mov'
case 'image/gif':
return 'gif'
default:
throw new Error(`Unsupported mime type: ${mimeType}`)
}
@@ -47,6 +49,8 @@ export function extToMime(ext: string) {
return 'video/mpeg'
case 'mov':
return 'video/quicktime'
case 'gif':
return 'image/gif'
default:
throw new Error(`Unsupported file extension: ${ext}`)
}

Some files were not shown because too many files have changed in this diff Show More