Compare commits

...

125 Commits

Author SHA1 Message Date
Samuel Newman 0081b37900 dispose of input once finished 2025-11-27 16:41:32 +02:00
Samuel Newman 0a7209c623 process videos on web with webcodecs 2025-11-27 16:31:19 +02:00
Anastasiya Uraleva d0649e9a9e [APP-1403] Profile empty states (#8969)
* update: Empty state component

* update type error fixes

* add empty state icon

* update translations on labels

* update: lint error for empty state

* remove unused prompt

* update type errors

* fix lint errors and update profile followers and profile follows

* updated starterpack

* fix lint errors

* address feedback

* update icons to be a react element

* update icons

* update viewbox for icons

* optimize: icons and update profile lists

* optimize: icons and update profile lists

* lint error fix

* update iconSize from the rebase
2025-11-27 03:22:03 -08:00
pfrazee 2d056e5be6 Nightly source-language update 2025-11-27 02:40:10 +00:00
Samuel Newman 7f2d46b731 use contentFit prop (#9452) 2025-11-26 09:06:12 -08:00
bnewbold 6ab3751b5c update golang to v1.25 (and some containers do debian bookworm) (#9442)
* bskyweb: bump go to v1.25

* update docker images to go v1.25 and debian bookworm

* bump CI to go v1.25
2025-11-25 10:35:50 -08:00
pfrazee 8424083de9 Nightly source-language update 2025-11-23 02:47:44 +00:00
Samuel Newman 54af170448 Use new blue everywhere (#9435) 2025-11-23 00:20:37 +02:00
Samuel Newman 8b00b8f945 Split React Native patches into individual patches (#9429) 2025-11-22 23:49:57 +02:00
tomsqrd 9136be68b3 Reading the optional String extras from attachment share intents (#9396)
and adding them to the compose intents for images and videos if they exist.

Co-authored-by: Tom Quinders <tom.quinders@check24.de>
2025-11-22 03:03:30 -08:00
Samuel Newman af13c70444 bump version to v1.111 (#9431) 2025-11-21 22:56:49 +02:00
Samuel Newman 9330e21c6a patch in upstream expo updates fix (#9428) 2025-11-21 07:15:23 -08:00
Samuel Newman 5d220e49a0 [v1.110.1 release prep] Translations (#9426)
* `yarn intl:pull`

* `yarn intl:extract:all`
2025-11-20 10:23:36 -08:00
Samuel Newman 31fac4a62b Use macos-26-xlarge runner, update actions to resolve caching issues (#9411)
* use xlarge runner for macos build

* try and fix yarn cache

* update actions/cache for pods step

* use expo github action main rather than v8

* update all actions to the same

* use yarn cache where missing
2025-11-20 10:07:38 -08:00
Samuel Newman 13c4ef83f1 Add minimal header to profiles (#8936) 2025-11-20 18:31:41 +02:00
Samuel Newman dfe9f9fa07 use slightly more rounded repost icon (#9424) 2025-11-20 18:00:14 +02:00
Samuel Newman 2919a7da7f Search lists cleanup, pagninate actor search (#9009) 2025-11-20 17:59:20 +02:00
Samuel Newman 2792b4cef0 prevent webview from showing during device attest (#9425) 2025-11-20 17:54:02 +02:00
Samuel Newman f5a5eee32c handle share intents when app first launches (#9410) 2025-11-20 17:52:33 +02:00
Samuel Newman 9823bc98e9 fix android 2fa input not being paste-able (#9407) 2025-11-20 17:49:58 +02:00
Samuel Newman f57268e9b8 Use useConfirmEmail hook in deep link verification flow (#9413)
* use useConfirmEmail hook in deep link verification flow

* more straightforward logic
2025-11-20 07:48:13 -08:00
Alex Benzer 002a63b125 Post view client event (#9408)
* Adds post:view client event tracking in feeds

* Add post:view event on the post page itself

* Don't send post:view to statsig for now

* convert to non reactive callback to reduce rerenders

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-11-20 07:41:56 -08:00
Samuel Newman 2faf62c7f7 Update version to v1.110.1 (#9416) 2025-11-19 13:21:56 -08:00
Eric Bailey 7b99d8c7c3 Update Android build runner (#9404) 2025-11-17 16:36:03 -06:00
Samuel Newman c2979ffefa re-extract languages (#9402) 2025-11-17 11:00:06 -08:00
Samuel Newman bc62b3da76 Add metrics for composer threadgate (#9401)
* add metrics for composer threadgate

* add metrics to the in-thread version

* fix type error
2025-11-17 10:51:01 -08:00
Samuel Newman 53b3bd93a5 [1.110.0 release prep] Translations (#9400)
* `yarn intl:pull`

* `yarn intl:extract:all`
2025-11-17 08:37:07 -08:00
Samuel Newman 7197a064d7 [v1.110] Release prep (#9399)
* yml formatting whatever

* fix date input color

* fix up report dialog tests

* dumb fix to bypass save password popup

* remove tooltip in e2e

* Update index.e2e.tsx

* skip gate in e2e
2025-11-17 08:01:02 -08:00
Samuel Newman ef97a20c23 Fix password autofill on iOS (#9397)
* fix password autofill on iOS

* update autocomplete props, rm textContentType
2025-11-17 06:27:00 -08:00
Samuel Newman f53d5b6f27 add haptics to segmented control (#9398) 2025-11-17 05:23:28 -08:00
Alex Benzer 084f60c88f Profile follow client events (#9385)
* Add parameters to profile:follow

Track who was followed, whose profile generated the follow, and the position of the person who was followed in the list

* Add profileCard:seen event

* Don't send "profileCard:seen" event to Statsig

* Clean up

* prevent overzealous clearing

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-11-17 05:04:11 -08:00
Alex Benzer 2f678a3ccd Adds a "follow back" button to follow notifications (#9359)
* Adds a "follow back" button to follow notifications

* get shadowcache logic working, strip out manual optimistic update

* whoops, don't just stick any old profile in there

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-11-17 05:00:26 -08:00
Samuel Newman d464cde412 add transparency prop to tab bar (#9336) 2025-11-17 04:59:56 -08:00
surfdude29 a35a81a508 Change text on threadgate button from 'Anybody' to 'Anyone' (#9390) 2025-11-17 03:27:43 -08:00
surfdude29 4145878819 remove accessibilityHint and tweak labels (#9391) 2025-11-17 03:26:39 -08:00
Alex Benzer fb058afa54 Add client event for clicks on desktop feeds (#9386) 2025-11-17 03:23:56 -08:00
pfrazee 185fd39092 Nightly source-language update 2025-11-15 02:38:20 +00:00
Samuel Newman 92926a2417 SegmentedControl component (#8606)
* new segmented control

* fix type error

* convert server input, use CSS for web

* add segmented control to storybook

* use segmented control in embed dialog

* add to suggested text wrappers

* update change handle dialog

* update styles since button changes

* fix atom

* style updates to segmented control, add size prop

* update state in layout effect rather than in render

* set type = 'radio' as default

* prevent expansion in server dialog on iOS

* use non reactive callback in needsUpdate effect
2025-11-14 11:43:28 -08:00
Samuel Newman 7a8ab551e3 update crop tool to latest, adjust to API changes (#9389) 2025-11-14 11:05:31 -08:00
Samuel Newman 1caac024ab Fix video letterboxing issue (#9339)
* give video a black background on web

* Video crop on web tweaks (#9371)

* Remove video embed crop option to reduce confusion

* Improve default thumb and border on web

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-11-14 10:29:49 -08:00
Samuel Newman 24c96ac881 Image saving - fix extension, increase download timeout (#9388) 2025-11-14 17:46:39 +02:00
Samuel Newman c92ac4abc1 flatten may return undefined (#9387) 2025-11-14 17:46:20 +02:00
Samuel Newman b422765aed [Threadgate] Tweak threadgate buttons (#9173)
* tweak in-post threadgate button

* tweak composer threadgate button

* reduce date length slightly

* pressed styles

* make date length depend on breakpoint

* add chevron to label btn

* add tiny chevron, special-case button icon width

* [Threadgate] Add hint (#9350)

* get tooltip working on web

* add compatibility layer for working in iOS sheets

* add timeout to profile tooltip now that it appears instantly

* rm debug code

* Update ThreadgateBtn.tsx

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

* remeasure when keyboard changes

---------

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

* fix types

* [Threadgate] Refresh dialog (#9342)

* wip new ui

* update threadgate dialog with new designs

* restore nobody option

* relayout android sheet when ratio changes

* fix ratio changing case in bottom sheet

* timebox reached, use setTimeout

* update panel styles

* missing imports

* extract out Panel

* tweak layout animation

* fix icon color

* use same color mechamism for icon as text

* restore the header

* refreshed toggle styles (#9343)

* [Threadgate] Persist settings (#9341)

* add persist toggle to threadgate dialog

* move state back down

* sort out spacing

* wire up query

* @surfdude29 tweaks

* use tiny chevron in WhoCanReply

* wait for prefetch before opening

* move Panel into the Toggle namespace

* default -> pref

* use medium date length

* rm hover state from web selects, fix border radius

* fix key issue in Selects

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2025-11-14 06:21:37 -08:00
Samuel Newman b56ee74cdb Tweak bg on follows you pill (#9384)
* tweak bg on follows you pill

* same for profile
2025-11-13 12:01:49 -08:00
Samuel Newman 554275957e update button spacing (#9383) 2025-11-13 09:24:27 -08:00
pfrazee 4884677826 Nightly source-language update 2025-11-13 02:41:07 +00:00
Samuel Newman e663e824b7 Add rectangular shape to button (#9382) 2025-11-13 00:35:34 +02:00
Samuel Newman 68a4d84450 tweak settings divider color (#9381) 2025-11-12 04:09:57 -08:00
pfrazee 9982961b8f Nightly source-language update 2025-11-12 02:40:28 +00:00
Eric Bailey 1711626493 Migrate remaining old report reasons, remove old unused file (#9378) 2025-11-11 14:13:11 -06:00
Samuel Newman cb2af3cd18 only round buttons if size is set (#9377) 2025-11-11 10:26:49 -06:00
Samuel Newman 59ffaa8a81 fix rounded notifs (#9374) 2025-11-11 03:18:43 -08:00
Samuel Newman a275e8999e restore flatten in emoji reaction picker (#9375) 2025-11-11 03:18:24 -08:00
pfrazee 8d2d45508d Nightly source-language update 2025-11-11 02:41:06 +00:00
Samuel Newman ddf82dcc44 Delete EmptyStateWithButton.tsx (#9348) 2025-11-10 18:02:52 -06:00
Samuel Newman 835a716eaf Try and guard against play/pause function being called after player is destroyed (#9364)
* try and guard play/pause against unmount

* add try/catch to avoid fatal crashes
2025-11-10 18:02:13 -06:00
Samuel Newman bb365856c9 fix react query error (#9365) 2025-11-10 18:02:01 -06:00
Samuel Newman bcd0e40192 Make the buttons round again (#9367)
* make the base button round

* some miscalleaneous tweaks
2025-11-10 18:01:41 -06:00
Samuel Newman 5208aa4a65 rm unused follow button (#9332) 2025-11-10 16:37:01 -06:00
surfdude29 1cdb105af5 tweak accessibility labels (#9347) 2025-11-10 16:31:08 -06:00
Samuel Newman ad91029f10 Select account redesign (#8905)
* new designs for select account

* tweak web styles

* add logged out indicator

* unify helper function

* move error, try catch
2025-11-10 06:02:53 -08:00
Alex Benzer 4791486aa7 Fixes a ts error caused by WelcomeModal styles on logged-out homepage (#9362)
* Fixes a ts error caused by welcomemodal styles on logged-out homepage

* use flatten util instead

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-11-10 03:18:31 -08:00
Samuel Newman df00ffa532 use feed module header bottom border on web (#9363) 2025-11-10 03:16:45 -08:00
pfrazee 70ccd82745 Nightly source-language update 2025-11-10 02:42:45 +00:00
Caidan 14604f735b fix: feed title line heights (#9346) 2025-11-09 18:24:12 +02:00
pfrazee 9583c310b9 Nightly source-language update 2025-11-07 02:40:28 +00:00
hailey d6ca9e6b41 [Reporting] Add new reporting categories and reasons (#9079)
* start adding new report reasons

* cleanup

* use updated types

* cleanup open/closed logic

* other fix

* state fix

* rename type

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/index.tsx

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* Update src/components/moderation/ReportDialog/utils/useReportOptions.ts

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

* add mapping

* Replace consts and maps using exported values

* Map to deprecated reason if necessary, send some reports only to Bluesky

* Use only new report reason types

* Cleanup

* Bump package, integrate new reasons

* Adjust sorting, return type, integrate feedback

* Just remove sort

* Finish 'other' handling

* Update descriptions

* Just invert filter logic, no functional change but feels more correct

* Clarify handling

* fix deep imports

* fix type error

* [Reporting] Integrate new `ReportDialog` into DMs (#9213)

* Update types, fork copy for convo vs message

* Integrate in remaining spots

* Replace DoneStep with separate dialog that opens after reporting

* Handle profile error state by just showing a success message

* Delete old dms ReportDialog

* Chats only go to bsky

* Only open block or delete dialog if submitted successfully

* make close button always large

* prevent expansion of blockordelete dialog

* Delete old report dialog (#9214)e

* Skip labeler selection for Bluesky-only cases

* Rename AfterReportDialog

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

---------

Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-11-06 15:40:13 -06:00
David Buchanan fd14bbfef9 move yt embed script to its own source file (#9337) 2025-11-06 10:15:49 -08:00
Samuel Newman 9f98e53541 Revert "push in post-post toast (#9252)" (#9344)
This reverts commit 54a388d132.
2025-11-06 09:40:31 -08:00
Eric Bailey 7bf00424c4 Clean up getPostThreadV2 settings/params in prep for future work (#9179)
* Deprecate prioritizeFollowedUsers

* Bump API package
2025-11-06 10:42:08 -06:00
Samuel Newman ec2c580aae Fix Android crash by try/catch dialog open (#9335) 2025-11-06 17:58:14 +02:00
pfrazee 215f97bad9 Nightly source-language update 2025-11-06 02:41:01 +00:00
Samuel Newman 531ff6c433 restore flatten to a place that is needed (#9334) 2025-11-05 02:07:01 -08:00
pfrazee 2077699a07 Nightly source-language update 2025-11-05 02:40:28 +00:00
Samuel Newman fb3f1c3936 always send #interactionReply (#9330) 2025-11-04 19:21:55 +02:00
Samuel Newman 78e20c4200 Prevent sparse arrays in snapToOffsets (#9329)
* prevent sparse arrays in snapToOffsets

* wait, sparse arrays don't work like that

* more readable check even if it's technically useless

* make the second check actually work
2025-11-04 09:08:43 -08:00
Eric Bailey d8e7e65a9d Post skele tweaks (#9328)
* feat: PostControlsSkeleton (#9205)

* feat: new PostControlsSkeleton component

* feat: integrate PostControlsSkeleton into other usages

* fix: shrink skele pill and circle sizes a bit

* Couple small tweaks

(cherry picked from commit f6a38ec42274bffc41b2898d8addb735791494fd)

---------

Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
2025-11-04 10:09:17 -06:00
Samuel Newman 16b14b9d13 Restore await to dismissBrowser in intent handler (#9326) 2025-11-04 16:57:52 +02:00
Samuel Newman fd5b1b3a59 add even more optional chaining to link thing (#9327) 2025-11-04 16:57:39 +02:00
Samuel Newman c9033dbc39 Avoid asking for read access to photo library when saving images on iOS (#9297)
* use saveToLibraryAsync on iOS

* use _(msg``) syntax
2025-11-04 06:33:30 -08:00
Samuel Newman 1b0fbd503e Remove use of flatten where possible (#9325)
* remove use of flatten where possible

* restore flatten where it says it's needed

* missing utils alpha
2025-11-04 05:43:08 -08:00
pfrazee 0bcde99025 Nightly source-language update 2025-11-04 02:40:47 +00:00
Alex Benzer 5a4e012fdc Adds client event for empty feed error (#9287)
* Adds client event for empty feed error

* Only log for discover

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-11-03 11:37:40 -06:00
Samuel Newman 6ccedd5e31 ship post follow follow suggestions (#9251) 2025-11-03 09:18:49 -08:00
Samuel Newman feab782ba9 Android lightbox minor perf improvement (#9274)
* minor perf improvement on android for lightbox

* Use correct `cachePolicy` when prefetching lightbox images (#9275)

* use `memory` cachePolicy when prefetching

* use `memory` cache policy on iOS lightbox images
2025-11-03 09:18:26 -08:00
Samuel Newman ca505fbbd2 chore: update caniuse (#9239) 2025-11-03 09:18:02 -08:00
Samuel Newman e5f3131644 fix right nav overflow (#9253) 2025-11-03 09:15:29 -08:00
Samuel Newman 54a388d132 push in post-post toast (#9252) 2025-11-03 09:15:12 -08:00
Samuel Newman b1f1b2050c add android ripple effect to images in feed (#9273) 2025-11-03 09:13:22 -08:00
Samuel Newman 9070fa9a8d Improve image download timeout handling (#9276) 2025-11-03 09:11:29 -08:00
Samuel Newman 55e9298ee8 disable wake-from-background OTA on Android (#9317) 2025-11-03 09:08:34 -08:00
Samuel Newman c6215065bd Update Android adaptive icon (#9298)
* remove adaptive icon gradient background

* try new foreground/monochrome assets

* adjust size back down
2025-11-03 09:05:18 -08:00
Samuel Newman bd3594ceea use 0.5px media border on high DPI screens on web (#9311) 2025-10-31 07:07:43 -07:00
Samuel Newman de6d2f1912 Update web dim <html> background color (#9307)
* update dim background color

* Update other css colors

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2025-10-30 11:44:22 -05:00
Jason Ericson faa49feec8 Fixed Apple Music song embeds. (#9220)
* Fixed Apple Music song embeds  when using a direct song link (vs playlist or album).

* Fixed song embed test.

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Jason Ericson <jasonmarkericson@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-30 09:25:42 -07:00
pfrazee 55b3474ad2 Nightly source-language update 2025-10-30 02:39:59 +00:00
Anastasiya Uraleva b0eb079ff8 [APP-1321]: Update brand embeds more clearly (#8955)
* Nightly source-language update

* run prettier

* wip

* feat: use local dev for bskyembed test.html

* feat: commit generated files which i'm not sure about

* Revert "feat: commit generated files which i'm not sure about"

This reverts commit b891efdaa1fd7ddd4417bfd5267e861a25a93b0e.

* chore: revert change to bskyembed/.eslintrc.cjs

* chore: remove generated file from bskyweb/static

* chore: revert messages.po changes

* Update bskyembed/test.html

* Nightly source-language update

* Update: Bluesky embeds

* update: embed colors for light and dark modes

* remove unused changes

* remove duplicate word

* update border radius and add cursor pointer

* add icons folder for dynamic icons

---------

Co-authored-by: Anastasiya Uraleva <anastasiya@Mac.localdomain>
Co-authored-by: Elijah Seed-Arita <elijaharita@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: Anastasiya Uraleva <anastasiya@Anastasiyas-MacBook-Pro.local>
2025-10-29 17:24:48 -07:00
Samuel Newman cfa2534ccb remove await on dismissBrowser (#9304) 2025-10-29 21:23:10 +02:00
Samuel Newman 2d626f7bbf Fix android icon file name (#9303) 2025-10-29 09:58:56 -07:00
Samuel Newman 5efae600dd Add dark/tinted icon variants (#9241)
* add tinted icon style

* new dark icon

* use icon composer file

* rename some icons, ensure default ios is still there

* use solid fill instead of auto gradient

* fix web build

* back to gradient

* add . for consistency

* rename `default_old` to `legacy`
2025-10-29 09:49:45 -07:00
Eric Bailey 339f3320c6 [TS-147] Add internal debug field display (#9302)
* Add internal debug field display

* Update title
2025-10-29 11:21:19 -05:00
Samuel Newman 3107efb449 Get rid of share menu fade edge effect (#9250) 2025-10-29 09:09:07 -07:00
Maxym Hyba faa389e060 rm clamp (#9271) 2025-10-29 08:46:21 -07:00
Samuel Newman f15a949ef8 more text elements missing emoji prop (#9301) 2025-10-29 08:44:52 -07:00
Samuel Newman bb908a5534 fix line height in hover card (#9281) 2025-10-29 08:44:17 -07:00
Samuel Newman a503fe27f5 add emoji prop to image alt text (#9278) 2025-10-29 08:43:53 -07:00
Chenyu d58c6c9ebc use admonition instead of toast for errors when appealing labels (#9272)
* use admonition instead of toast for errors when appealing labels

* address commments
2025-10-29 06:48:23 -07:00
Caidan bd5d102826 fix: prevent profile suggestions animation when no data available (#9277)
Prevents AccordionAnimation from animating open on web when suggestions array is empty by adding conditional rendering logic to AnimatedProfileHeaderSuggestedFollows component.
2025-10-29 02:43:18 -07:00
pfrazee 32bf6cf01f Nightly source-language update 2025-10-29 02:41:25 +00:00
Samuel Newman e3f5967407 Android - add fallbacks when saving image to gallery (#9293)
* add fallback paths when saving image to gallery

* `debug` -> `info`
2025-10-28 09:58:41 -07:00
Samuel Newman 900e569ef2 Override Reduce Motion on load-bearing animation (#9294) 2025-10-28 09:52:31 -07:00
Samuel Newman 1883f3fc7b Close IAB when it triggers an intent (#8454)
* Close IAB if it opens a link to the app

* native only

* await close
2025-10-28 08:52:09 -07:00
Samuel Newman 5cff45bdc8 Update expo and related dependencies (#9291)
* update expo + deps

* reanimated patch right version

* Update yarn.lock

* update other rn packages
2025-10-28 06:44:16 -07:00
Samuel Newman 0c6469f272 Change adaptive icon background color (#9286) 2025-10-28 00:26:52 -07:00
pfrazee b98350f927 Nightly source-language update 2025-10-28 02:38:50 +00:00
Samuel Newman 206f52fb13 Bump version to 1.110.0 (#9284) 2025-10-27 21:55:55 +02:00
Samuel Newman 78c8a6037f Remove feed:seen from statsig (#9261) 2025-10-27 13:23:38 -05:00
bnewbold 28e132ae03 clarify in content hider if label is on overall account (#9264)
* clarify in content hider if label is on overall account

* fix bool typo

* Update src/components/moderation/ContentHider.tsx

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

* rename variable for clarity

---------

Co-authored-by: Samuel Newman <mozzius@protonmail.com>
2025-10-27 10:37:47 -07:00
Samuel Newman 25a2a2c7f6 Revert "Disable Sentry sampling for now (#9262)" (#9282)
This reverts commit de68915bc0.
2025-10-27 11:12:27 -05:00
Eric Bailey 2a4b825637 Release 1.109.0 i18n take III (#9269) 2025-10-23 11:27:49 -05:00
Eric Bailey c00b3d1050 Don't log errors for known app password issues in chat (#9268) 2025-10-23 11:18:53 -05:00
Eric Bailey 74cb81b65f Add config to disable update overlay, disable it (#9267) 2025-10-23 11:16:55 -05:00
Samuel Newman e28aa7d1ab Patch out PerformanceMonitor component (#9266)
* patch out perf monitor component

* make PerformanceMonitor a noop

* rm debug stuff
2025-10-23 10:35:37 -05:00
Eric Bailey d4d5d6d8b9 intl:pull and intl:extract:all (#9263) 2025-10-22 15:51:19 -05:00
Eric Bailey de68915bc0 Disable Sentry sampling for now (#9262) 2025-10-22 14:08:25 -05:00
349 changed files with 55133 additions and 48297 deletions
+1
View File
@@ -43,6 +43,7 @@ module.exports = {
suggestedTextWrappers: {
Button: 'ButtonText',
'ToggleButton.Button': 'ToggleButton.ButtonText',
'SegmentedControl.Item': 'SegmentedControl.ItemText',
},
},
],
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
@@ -23,7 +23,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
@@ -22,7 +22,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
@@ -3,9 +3,9 @@ on:
workflow_dispatch:
pull_request:
paths:
- 'bskylink/**'
- 'Dockerfile.bskylink'
- '.github/workflows/build-and-push-link-aws.yaml'
- "bskylink/**"
- "Dockerfile.bskylink"
- ".github/workflows/build-and-push-link-aws.yaml"
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
@@ -3,9 +3,9 @@ on:
workflow_dispatch:
pull_request:
paths:
- 'bskyogcard/**'
- 'Dockerfile.bskyogcard'
- '.github/workflows/build-and-push-ogcard-aws.yaml'
- "bskyogcard/**"
- "Dockerfile.bskyogcard"
- ".github/workflows/build-and-push-ogcard-aws.yaml"
env:
REGISTRY: ${{ secrets.AWS_ECR_REGISTRY_USEAST2_PACKAGES_REGISTRY }}
@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Docker buildx
uses: docker/setup-buildx-action@v1
+4 -4
View File
@@ -15,7 +15,7 @@ jobs:
build:
if: github.repository == 'bluesky-social/social-app'
name: Build and Submit Android
runs-on: ubuntu-latest
runs-on: Linux-x64-32core
steps:
- name: Check for EXPO_TOKEN
run: >
@@ -30,7 +30,7 @@ jobs:
fetch-depth: 5
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
@@ -39,7 +39,7 @@ jobs:
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
with:
expo-version: latest
eas-version: latest
@@ -54,7 +54,7 @@ jobs:
java-version: "17"
- name: ⚙️ Install dependencies
run: yarn install
run: yarn install --frozen-lockfile
- name: 🔤 Compile translations
run: yarn intl:build 2>&1 | tee i18n.log
+5 -5
View File
@@ -15,7 +15,7 @@ jobs:
build:
if: github.repository == 'bluesky-social/social-app'
name: Build and Submit iOS
runs-on: macos-26
runs-on: macos-26-xlarge
steps:
- name: Check for EXPO_TOKEN
run: >
@@ -30,7 +30,7 @@ jobs:
fetch-depth: 5
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
@@ -39,7 +39,7 @@ jobs:
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
with:
expo-version: latest
eas-version: latest
@@ -49,7 +49,7 @@ jobs:
run: yarn global add eas-cli-local-build-plugin
- name: ⚙️ Install dependencies
run: yarn install
run: yarn install --frozen-lockfile
- uses: maxim-lobanov/setup-xcode@v1
with:
@@ -61,7 +61,7 @@ jobs:
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
uses: actions/cache@v4
id: pods-cache
with:
path: ./ios/Pods
@@ -55,7 +55,7 @@ jobs:
run: git fetch origin main:main --depth 100
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
@@ -86,7 +86,7 @@ jobs:
run: yarn typecheck
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
if: ${{ !steps.fingerprint.outputs.includes-changes }}
with:
expo-version: latest
@@ -172,13 +172,13 @@ jobs:
fetch-depth: 5
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
with:
expo-version: latest
eas-version: latest
@@ -188,7 +188,7 @@ jobs:
run: yarn global add eas-cli-local-build-plugin
- name: ⚙️ Install dependencies
run: yarn install
run: yarn install --frozen-lockfile
- uses: maxim-lobanov/setup-xcode@v1
with:
@@ -200,7 +200,7 @@ jobs:
version: 1.16.2
- name: 💾 Cache Pods
uses: actions/cache@v3
uses: actions/cache@v4
id: pods-cache
with:
path: ./ios/Pods
@@ -276,13 +276,13 @@ jobs:
fetch-depth: 5
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
with:
expo-version: latest
eas-version: latest
@@ -297,7 +297,7 @@ jobs:
java-version: "17"
- name: ⚙️ Install dependencies
run: yarn install
run: yarn install --frozen-lockfile
- name: 🔤 Compile translations
run: yarn intl:build
+5 -5
View File
@@ -7,7 +7,7 @@ on:
- main
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
@@ -15,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Git Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go tooling
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: "1.25"
- name: Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Check
@@ -32,11 +32,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Git Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up Go tooling
uses: actions/setup-go@v3
with:
go-version: '1.23'
go-version: "1.25"
- name: Dummy Static Files
run: touch bskyweb/static/js/blah.js && touch bskyweb/static/css/blah.txt && touch bskyweb/static/media/blah.txt
- name: Lint
+7 -5
View File
@@ -6,7 +6,7 @@ on:
branches:
- main
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
@@ -15,11 +15,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: Yarn install
uses: Wandalen/wretry.action@master
with:
@@ -41,11 +42,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: Yarn install
uses: Wandalen/wretry.action@master
with:
@@ -1,7 +1,7 @@
name: Nightly Update Source Languages
on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
- cron: "0 2 * * *" # run at 2 AM UTC
workflow_dispatch:
jobs:
@@ -16,13 +16,14 @@ jobs:
steps:
- name: Check out Git repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
- name: Install node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: Yarn install
uses: Wandalen/wretry.action@master
with:
@@ -46,4 +47,4 @@ jobs:
push_sources: false
create_pull_request: false
env:
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+2 -2
View File
@@ -116,7 +116,7 @@ jobs:
ref: ${{ steps.pr-info.outputs.head-sha }}
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
@@ -140,7 +140,7 @@ jobs:
run: yarn typecheck
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
uses: expo/expo-github-action@main
with:
expo-version: latest
eas-version: latest
+5 -5
View File
@@ -29,7 +29,7 @@ jobs:
fetch-depth: 0
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
@@ -82,9 +82,9 @@ jobs:
id: get-diff
uses: NejcZdovc/bundle-size-diff@v1
with:
base_path: 'stats-base.json'
pr_path: '../stats-new.json'
excluded_assets: '(.+).chunk.js|(.+).js.map|(.+).json|(.+).png'
base_path: "stats-base.json"
pr_path: "../stats-new.json"
excluded_assets: "(.+).chunk.js|(.+).js.map|(.+).json|(.+).png"
- name: 💬 Drop a comment
uses: marocchino/sticky-pull-request-comment@v2
@@ -110,7 +110,7 @@ jobs:
if: github.event_name == 'pull_request'
- name: 🔧 Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
+3 -3
View File
@@ -3,7 +3,7 @@ name: Lockfile
on:
pull_request:
concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out PR HEAD
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
@@ -20,7 +20,7 @@ jobs:
run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Install node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
+5
View File
@@ -119,3 +119,8 @@ src/locale/locales/**/*.js
# ogcard assets
bskyogcard/src/assets/fonts/noto-*
# bskyweb build output
bskyweb/static/media/*.webp
bskyweb/static/media/*.jpg
bskyweb/static/media/*.png
+2 -2
View File
@@ -1,4 +1,4 @@
FROM golang:1.24.5-bullseye AS build-env
FROM golang:1.25-bookworm AS build-env
WORKDIR /usr/src/social-app
@@ -89,7 +89,7 @@ RUN cd bskyweb/ && \
-o /bskyweb \
./cmd/bskyweb
FROM debian:bullseye-slim
FROM debian:bookworm-slim
ENV GODEBUG=netdns=go
ENV TZ=Etc/UTC
+2 -2
View File
@@ -1,4 +1,4 @@
FROM golang:1.24.5-bullseye AS build-env
FROM golang:1.25-bookworm AS build-env
WORKDIR /usr/src/social-app
@@ -58,7 +58,7 @@ RUN cd bskyweb/ && \
-o /embedr \
./cmd/embedr
FROM debian:bullseye-slim
FROM debian:bookworm-slim
ENV GODEBUG=netdns=go
ENV TZ=Etc/UTC
+8 -2
View File
@@ -31,9 +31,15 @@ appId: xyz.blueskyweb.app
text: "Your account"
- tapOn:
id: "nextBtn"
- waitForAnimationToEnd
- tapOn:
text: "Not Now"
optional: true
# save password thing no longer seems to be in hierarchy
# tap on something else instead
point: 20%,20%
repeat: 2
delay: 1000
- tapOn:
id: "handleInput"
- inputText: "e2e-test"
- extendedWaitUntil:
visible:
+3 -3
View File
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: ?users&follows&posts&feeds
SERVER_PATH: ?users&follows&posts&feeds
- runFlow:
file: ../setupApp.yml
- tapOn:
@@ -11,7 +11,7 @@ appId: xyz.blueskyweb.app
- extendedWaitUntil:
visible:
text: "Feeds ✨"
text: "Feeds ✨"
- tapOn:
label: "Can go to feeds page using feeds button in tab bar"
text: "Feeds ✨"
@@ -50,6 +50,6 @@ appId: xyz.blueskyweb.app
label: "Can delete posts"
id: "postDropdownBtn"
childOf:
id: "feedItem-by-alice.test"
id: "feedItem-by-alice.test"
- tapOn: "Delete post"
- tapOn: "Delete"
+2 -1
View File
@@ -17,7 +17,8 @@ appId: xyz.blueskyweb.app
index: 0
- tapOn:
id: "postDropdownReportBtn"
- tapOn: "Create report for Misleading Post"
- tapOn: "Create report for Misleading"
- tapOn: "Create report for Spam"
- tapOn: "Send report to Dev-env Moderation"
- tapOn:
point: "50%,90%"
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../../setupServer.js
env:
SERVER_PATH: ?users&follows&posts&feeds
SERVER_PATH: ?users&follows&posts&feeds
- runFlow:
file: ../../setupApp.yml
- tapOn:
@@ -15,9 +15,11 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "profileHeaderDropdownReportBtn"
- tapOn:
id: "report:option:com.atproto.moderation.defs#reasonSpam"
id: "report:category:Misleading"
- tapOn:
id: "report:option:Spam"
- assertVisible:
id: "report:labeler:mod-authority.test"
text: "Send report to Dev-env Moderation"
- tapOn:
id: "report:submit"
- assertNotVisible:
+5 -3
View File
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../../setupServer.js
env:
SERVER_PATH: ?users&follows&posts&feeds
SERVER_PATH: ?users&follows&posts&feeds
- runFlow:
file: ../../setupApp.yml
- tapOn:
@@ -15,9 +15,11 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "postDropdownReportBtn"
- tapOn:
id: "report:option:com.atproto.moderation.defs#reasonSpam"
id: "report:category:Misleading"
- tapOn:
id: "report:option:Spam"
- assertVisible:
id: "report:labeler:mod-authority.test"
text: "Send report to Dev-env Moderation"
- tapOn:
id: "report:submit"
- assertNotVisible:
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../../setupServer.js
env:
SERVER_PATH: ?users&follows&posts&feeds
SERVER_PATH: ?users&follows&posts&feeds
- runFlow:
file: ../../setupApp.yml
- tapOn:
@@ -15,20 +15,28 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "postDropdownReportBtn"
- tapOn:
id: "report:option:com.atproto.moderation.defs#reasonOther"
id: "report:category:Misleading"
- tapOn:
id: "report:option:Other misleading content"
- assertVisible:
id: "report:labeler:mod-authority.test"
text: Your report will be sent to Dev-env Moderation.*
# reason "other" defaults with details open
- assertVisible:
id: "report:details"
- tapOn:
id: "report:clearOption"
id: "report:clearReportOption"
- assertNotVisible:
id: "report:details"
- tapOn:
id: "report:option:com.atproto.moderation.defs#reasonSpam"
id: "report:clearCategory"
- assertNotVisible:
id: "report:option:Other misleading content"
- tapOn:
id: "report:category:Misleading"
- tapOn:
id: "report:option:Spam"
- assertVisible:
id: "report:labeler:mod-authority.test"
text: Your report will be sent to Dev-env Moderation.*
- tapOn:
id: "report:submit"
- assertNotVisible:
@@ -3,7 +3,7 @@ appId: xyz.blueskyweb.app
- runScript:
file: ../../setupServer.js
env:
SERVER_PATH: ?users&follows&posts&feeds
SERVER_PATH: ?users&follows&posts&feeds
- runFlow:
file: ../../setupApp.yml
- tapOn:
@@ -15,9 +15,11 @@ appId: xyz.blueskyweb.app
- tapOn:
id: "postDropdownReportBtn"
- tapOn:
id: "report:option:com.atproto.moderation.defs#reasonOther"
id: "report:category:Misleading"
- tapOn:
id: "report:option:Other misleading content"
- assertVisible:
id: "report:labeler:mod-authority.test"
text: "Send report to Dev-env Moderation"
# reason "other" defaults with details open
- assertVisible:
id: "report:details"
+1 -2
View File
@@ -1,4 +1,3 @@
# flow.yaml
appId: xyz.blueskyweb.app
@@ -33,7 +32,7 @@ appId: xyz.blueskyweb.app
direction: "LEFT"
- swipe:
direction: "LEFT"
- swipe:
- swipe:
direction: "LEFT"
- swipe:
direction: "RIGHT"
+1 -1
View File
@@ -1,4 +1,4 @@
import {LikelyType, getLikelyType} from '../../src/lib/link-meta/link-meta'
import {getLikelyType, LikelyType} from '../../src/lib/link-meta/link-meta'
describe('getLikelyType', () => {
it('correctly handles non-parsed url', async () => {
+6
View File
@@ -378,6 +378,7 @@ describe('parseEmbedPlayerFromUrl', () => {
'https://music.apple.com/us/playlist/playlistName/playlistId',
'https://music.apple.com/us/album/albumName/albumId',
'https://music.apple.com/us/album/albumName/albumId?i=songId',
'https://music.apple.com/us/song/songName/songId',
'https://vimeo.com/videoId',
'https://vimeo.com/videoId?autoplay=0',
@@ -604,6 +605,11 @@ describe('parseEmbedPlayerFromUrl', () => {
playerUri:
'https://embed.music.apple.com/us/album/albumName/albumId?i=songId',
},
{
type: 'apple_music_song',
source: 'appleMusic',
playerUri: 'https://embed.music.apple.com/us/song/songName/songId',
},
{
type: 'vimeo_video',
+11 -13
View File
@@ -11,7 +11,7 @@ module.exports = function (_config) {
*
* @see https://docs.expo.dev/build-reference/variables/#built-in-environment-variables
*/
const PLATFORM = process.env.EAS_BUILD_PLATFORM
const PLATFORM = process.env.EAS_BUILD_PLATFORM ?? 'web'
const IS_TESTFLIGHT = process.env.EXPO_PUBLIC_ENV === 'testflight'
const IS_PRODUCTION = process.env.EXPO_PUBLIC_ENV === 'production'
@@ -50,6 +50,10 @@ module.exports = function (_config) {
config: {
usesNonExemptEncryption: false,
},
icon:
PLATFORM === 'web' // web build doesn't like .icon files
? './assets/app-icons/ios_icon_default_next.png'
: './assets/app-icons/ios_icon_default.icon',
infoPlist: {
UIBackgroundModes: ['remote-notification'],
NSCameraUsageDescription:
@@ -174,9 +178,8 @@ module.exports = function (_config) {
icon: './assets/app-icons/android_icon_default_next.png',
adaptiveIcon: {
foregroundImage: './assets/icon-android-foreground.png',
monochromeImage: './assets/icon-android-foreground.png',
backgroundImage: './assets/icon-android-background.png',
backgroundColor: '#1185FE',
monochromeImage: './assets/icon-android-monochrome.png',
backgroundColor: '#006AFF',
},
googleServicesFile: './google-services.json',
package: 'xyz.blueskyweb.app',
@@ -323,18 +326,13 @@ module.exports = function (_config) {
* Default set
*/
default_light: {
ios: './assets/app-icons/ios_icon_default_light.png',
android: './assets/app-icons/android_icon_default_light.png',
ios: './assets/app-icons/ios_icon_legacy_light.png',
android: './assets/app-icons/android_icon_legacy_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,
},
next: {
ios: './assets/app-icons/ios_icon_default_next.png',
android: './assets/app-icons/android_icon_default_next.png',
ios: './assets/app-icons/ios_icon_legacy_dark.png',
android: './assets/app-icons/android_icon_legacy_dark.png',
prerendered: true,
},

Before

Width:  |  Height:  |  Size: 467 KiB

After

Width:  |  Height:  |  Size: 467 KiB

Before

Width:  |  Height:  |  Size: 432 KiB

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 KiB

@@ -0,0 +1,31 @@
{
"fill" : {
"automatic-gradient" : "srgb:0.00000,0.41569,1.00000,1.00000"
},
"groups" : [
{
"layers" : [
{
"fill" : "none",
"glass" : false,
"image-name" : "iOS transparent.png",
"name" : "iOS transparent"
}
],
"shadow" : {
"kind" : "neutral",
"opacity" : 0.5
},
"translucency" : {
"enabled" : true,
"value" : 0.5
}
}
],
"supported-platforms" : {
"circles" : [
"watchOS"
],
"squares" : "shared"
}
}

Before

Width:  |  Height:  |  Size: 389 KiB

After

Width:  |  Height:  |  Size: 389 KiB

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 38"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M22.333 31.667H45M22.333 6.333H45m-33.333 0A5.333 5.333 0 1 1 1 6.333a5.333 5.333 0 0 1 10.667 0Zm0 25.334a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z"/></svg>

After

Width:  |  Height:  |  Size: 333 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 62 53"><path fill="#405168" d="M28.173.231a5.653 5.653 0 0 1 7.018 3.83l2.66 9.046a20 20 0 0 1 3.986-.397c11.026 0 19.964 8.937 19.964 19.962l-.006.516c-.274 10.787-9.104 19.448-19.958 19.448l-.514-.007c-8.332-.21-15.394-5.528-18.178-12.938l-8.805 2.59a5.654 5.654 0 0 1-7.02-3.83L.232 14.34a5.654 5.654 0 0 1 3.83-7.018L28.172.23ZM41.838 14.71c-1.17 0-2.313.111-3.42.325l3.863 13.137a5.653 5.653 0 0 1-3.83 7.019L25.07 39.126c2.593 6.732 9.122 11.51 16.768 11.51 9.92 0 17.963-8.043 17.963-17.964S51.758 14.71 41.837 14.71ZM33.271 4.624a3.653 3.653 0 0 0-4.535-2.474L4.624 9.24a3.653 3.653 0 0 0-2.475 4.535l7.09 24.113a3.654 3.654 0 0 0 4.536 2.475l8.762-2.577a20 20 0 0 1-.662-5.114c0-8.961 5.905-16.544 14.037-19.069l-2.64-8.98Zm3.204 10.899c-7.302 2.28-12.601 9.096-12.601 17.15 0 1.571.203 3.095.582 4.548l13.431-3.948a3.654 3.654 0 0 0 2.474-4.536l-3.886-13.214Z"/></svg>

After

Width:  |  Height:  |  Size: 944 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 48 48"><path fill="#405168" d="M19.667 4.458a1 1 0 1 0 0-2v2Zm25 23a1 1 0 0 0-2 0h2ZM3.912 45.543l.454-.891-.454.89Zm-2.33-2.33.89-.455h0l-.89.454Zm39.173 2.33-.454-.891h0l.454.89Zm2.33-2.33-.89-.455.89.454ZM1.581 6.37l-.89-.454h0l.89.454Zm2.331-2.331-.454-.891.454.89ZM14.333 32.79h-1a1 1 0 0 0 1 1v-1Zm.781-8.781.707.707-.707-.707ZM36.562 2.562l-.707-.707v0l.707.707Zm7.543 0-.707.707v0l.707-.707Zm.457.458.707-.707v0l-.707.707Zm0 7.542.707.707-.707-.707ZM23.114 32.01l.707.707-.707-.707Zm12.02 14.114v-1h-25.6v2h25.6v-1ZM1 37.591h1v-25.6H0v25.6h1ZM9.533 3.458v1h10.134v-2H9.533v1Zm34.134 24h-1V37.59h2V27.457h-1ZM9.533 46.124v-1c-1.51 0-2.582 0-3.421-.07-.828-.067-1.34-.195-1.746-.402l-.454.89-.454.892c.735.374 1.54.537 2.491.614.94.077 2.107.076 3.584.076v-1ZM1 37.591H0c0 1.477 0 2.645.076 3.584.078.951.24 1.756.614 2.491l.891-.454.891-.454c-.207-.406-.335-.918-.403-1.746C2.001 40.173 2 39.101 2 37.591H1Zm2.912 7.952.454-.891a4.33 4.33 0 0 1-1.894-1.894l-.89.454-.892.454a6.33 6.33 0 0 0 2.768 2.768l.454-.891Zm31.221.581v1c1.477 0 2.645.001 3.585-.076.95-.078 1.756-.24 2.49-.614l-.453-.891-.454-.891c-.406.207-.919.335-1.746.403-.84.068-1.912.07-3.422.07v1Zm8.534-8.533h-1c0 1.51-.001 2.582-.07 3.421-.067.828-.196 1.34-.403 1.746l.891.454.891.454c.375-.735.537-1.54.615-2.49.076-.94.076-2.108.076-3.585h-1Zm-2.912 7.952.454.89a6.33 6.33 0 0 0 2.767-2.767l-.89-.454-.892-.454a4.33 4.33 0 0 1-1.893 1.894l.454.89ZM1 11.99h1c0-1.51 0-2.582.07-3.422.067-.827.195-1.34.402-1.745l-.89-.454-.892-.454c-.374.734-.536 1.54-.614 2.49C-.001 9.345 0 10.513 0 11.99h1Zm8.533-8.533v-1c-1.477 0-2.645-.001-3.584.076-.951.077-1.756.24-2.49.614l.453.89.454.892c.406-.207.918-.336 1.746-.403.839-.069 1.911-.07 3.421-.07v-1ZM1.581 6.37l.891.454A4.33 4.33 0 0 1 4.366 4.93l-.454-.891-.454-.891A6.33 6.33 0 0 0 .69 5.916l.891.454Zm12.752 19.525h-1v6.896h2v-6.896h-1Zm0 6.896v1h6.896v-2h-6.896v1Zm.781-8.781.707.707L37.27 3.269l-.707-.707-.707-.707-21.448 21.448.707.707Zm28.99-21.448-.706.707.457.458.707-.707.707-.707-.457-.458-.707.707Zm.458 8-.707-.707-21.448 21.448.707.707.707.707L45.27 11.269l-.707-.707Zm0-7.542-.707.707a4.333 4.333 0 0 1 0 6.128l.707.707.707.707a6.333 6.333 0 0 0 0-8.956l-.707.707Zm-8-.458.707.707a4.333 4.333 0 0 1 6.129 0l.707-.707.707-.707a6.333 6.333 0 0 0-8.957 0l.707.707ZM21.23 32.791v1c.972 0 1.905-.386 2.593-1.074l-.708-.707-.707-.707a1.67 1.67 0 0 1-1.178.488v1Zm-6.896-6.896h1c0-.442.176-.866.489-1.178l-.708-.707-.707-.707a3.67 3.67 0 0 0-1.074 2.592h1Z"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.333 1 9 45M37 1l-5.333 44M1 11.667h44m0 22.666H1"/></svg>

After

Width:  |  Height:  |  Size: 224 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 50 45"><path stroke="#405168" stroke-linejoin="round" stroke-width="2" d="M49 17c0 15.333-22 26.667-24 26.667S1 32.333 1 17C1 6.333 7.667 1 14.333 1S25 5 25 5s4-4 10.667-4S49 6.333 49 17Z"/></svg>

After

Width:  |  Height:  |  Size: 262 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#080B12" stroke-linecap="round" stroke-width="1.5" d="m1.417 28.645 7.586-5.676a5.33 5.33 0 0 1 6.867.809c3.98 4.286 8.594 8.182 14.88 8.182 5.794 0 9.633-2.147 13.333-5.847m-38 18.637h33.334a5.333 5.333 0 0 0 5.333-5.333V6.083A5.333 5.333 0 0 0 39.417.75H6.083A5.333 5.333 0 0 0 .75 6.083v33.334a5.333 5.333 0 0 0 5.333 5.333ZM36.75 14.083a5.333 5.333 0 1 1-10.667 0 5.333 5.333 0 0 1 10.667 0Z"/></svg>

After

Width:  |  Height:  |  Size: 491 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 50 50"><path stroke="#405168" stroke-linecap="square" stroke-linejoin="round" stroke-width="2" d="M9 1h32a8 8 0 0 1 8 8v21.333a8 8 0 0 1-8 8H27.667L14.333 49V38.333H9a8 8 0 0 1-8-8V9a8 8 0 0 1 8-8Z"/></svg>

After

Width:  |  Height:  |  Size: 272 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 43 48"><path stroke="#405168" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.603 46.333H3.532c-1.572 0-2.816-1.358-2.472-2.891 2.033-9.046 9.421-15.775 19.543-15.775q1.367 0 2.666.16m18.667 7.84L36.603 41m0 0-5.334 5.333M36.603 41l-5.334-5.333M36.603 41l5.333 5.333m-12-36A9.333 9.333 0 1 1 20.603 1a9.333 9.333 0 0 1 9.333 9.333Z"/></svg>

After

Width:  |  Height:  |  Size: 429 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M10.928 18.882a1.95 1.95 0 0 0 2.452-.25l9-9a1.953 1.953 0 0 0-2.76-2.76L12 14.493l-7.62-7.62a1.952 1.952 0 0 0-2.76 2.76l9 9 .308.25Z"/></svg>

After

Width:  |  Height:  |  Size: 237 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 46 46"><path stroke="#405168" stroke-linecap="square" stroke-linejoin="round" stroke-width="2" d="M1 23h10.667M1 23V12m0 11v11m10.667-11h22.666m-22.666 0v11m0-11V12m22.666 11H45m-10.667 0v12.222m0-12.222V12M45 23V12m0 11v12.222M34.333 45h5.334A5.333 5.333 0 0 0 45 39.667v-4.445M34.333 45v-9.778m0 9.778H11.667M34.333 1h5.334A5.333 5.333 0 0 1 45 6.333V12M34.333 1v11m0-11H11.667m22.666 11H45M34.333 35.222H45M11.667 45H6.333A5.333 5.333 0 0 1 1 39.667V34m10.667 11V34m0-33H6.333A5.333 5.333 0 0 0 1 6.333V12M11.667 1v11M1 12h10.667M1 34h10.667"/></svg>

After

Width:  |  Height:  |  Size: 619 B

+10
View File
@@ -0,0 +1,10 @@
<svg width="105" height="32" viewBox="0 0 105 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.7901 9.77492C21.7947 11.2889 23.9508 14.3587 24.7425 16.0059C25.5342 14.3587 27.6903 11.2889 29.6949 9.77492C31.1413 8.68251 33.485 7.83725 33.485 10.5269C33.485 11.064 33.1789 15.0393 32.9993 15.6848C32.3752 17.9285 30.1009 18.5008 28.0778 18.1544C31.6141 18.7598 32.5136 20.7653 30.5709 22.7708C26.1996 27.2831 24.7425 20.3569 24.7425 20.3569C24.7425 20.3569 23.2854 27.2831 18.9142 22.7708C16.9714 20.7653 17.871 18.7598 21.4072 18.1544C19.3841 18.5008 17.1099 17.9285 16.4857 15.6848C16.3061 15.0393 16 11.064 16 10.5269C16 7.83725 18.3437 8.68251 19.7901 9.77492Z" fill="white"/>
<path d="M44.3863 16.3646C45.5901 16.7932 46.2292 17.7837 46.2292 18.8924C46.2292 20.7698 44.9659 21.908 42.5434 21.908H37.5942V11.3828H42.3799C44.6835 11.3828 45.7982 12.5506 45.7982 14.1028C45.7982 15.1376 45.3226 15.8915 44.3863 16.3646ZM42.2313 13.0237H39.6006V15.7732H42.2313C43.2568 15.7732 43.8067 15.2411 43.8067 14.3541C43.8067 13.5411 43.2419 13.0237 42.2313 13.0237ZM39.6006 20.2524H42.4393C43.5689 20.2524 44.1782 19.735 44.1782 18.8037C44.1782 17.828 43.5986 17.3402 42.4393 17.3402H39.6006V20.2524Z" fill="white"/>
<path d="M49.0111 21.908H47.1385V11.3828H49.0111V21.908Z" fill="white"/>
<path d="M55.2202 18.5524V14.2802H57.0929V21.908H55.2797V20.7993C54.7 21.6567 53.8975 22.0854 52.872 22.0854C51.252 22.0854 50.1968 21.1098 50.1968 19.3359V14.2802H52.0694V19.0254C52.0694 19.9863 52.545 20.4741 53.5111 20.4741C54.4177 20.4741 55.2202 19.8089 55.2202 18.5524Z" fill="white"/>
<path d="M65.6167 18.2272V18.6706H59.8799C60.0137 19.9863 60.7419 20.6367 61.8714 20.6367C62.7334 20.6367 63.3131 20.2672 63.6252 19.5428H65.4235C65.0222 21.095 63.6846 22.0854 61.8566 22.0854C60.7122 22.0854 59.7907 21.7159 59.0922 20.9915C58.3937 20.2672 58.037 19.3063 58.037 18.0941C58.037 16.8967 58.3788 15.9359 59.0773 15.1967C59.7759 14.4724 60.6825 14.1028 61.8269 14.1028C62.9861 14.1028 63.9076 14.4872 64.5912 15.2411C65.2749 15.995 65.6167 17.0002 65.6167 18.2272ZM61.812 15.5515C60.7865 15.5515 60.0731 16.1428 59.8948 17.3698H63.7441C63.5806 16.2611 62.8969 15.5515 61.812 15.5515Z" fill="white"/>
<path d="M69.7673 22.115C67.5379 22.115 66.3638 21.2428 66.2598 19.4837H68.0878C68.1918 20.4298 68.6674 20.7846 69.797 20.7846C70.8076 20.7846 71.3129 20.4741 71.3129 19.868C71.3129 19.3211 70.9562 19.055 69.8118 18.8628L68.935 18.715C67.2555 18.4341 66.4232 17.6654 66.4232 16.4089C66.4232 14.975 67.5676 14.1028 69.6038 14.1028C71.7885 14.1028 72.9181 14.9602 72.9924 16.6898H71.2238C71.1792 15.7585 70.6293 15.4332 69.6038 15.4332C68.712 15.4332 68.2662 15.7289 68.2662 16.3202C68.2662 16.8524 68.6526 17.0889 69.5443 17.2515L70.5104 17.3993C72.3681 17.7393 73.1707 18.4193 73.1707 19.7202C73.1707 21.2576 71.9223 22.115 69.7673 22.115Z" fill="white"/>
<path d="M81.3899 21.908H79.2497L77.0204 18.3602L75.8611 19.5132V21.908H74.0182V11.3828H75.8611V17.4437L78.9822 14.2802H81.2116L78.3134 17.1628L81.3899 21.908Z" fill="white"/>
<path d="M86.2805 16.2019L86.9047 14.2802H88.8665L85.909 22.5289C85.5968 23.3715 85.2104 23.9776 84.72 24.3176C84.2295 24.6576 83.531 24.8202 82.6095 24.8202C82.2974 24.8202 82.0299 24.8054 81.7921 24.7759V23.3124H82.5055C83.3526 23.3124 83.7688 22.795 83.7688 22.0854C83.7688 21.7306 83.6499 21.2132 83.4121 20.548L81.1827 14.2802H83.204L83.8282 16.1872C84.289 17.6211 84.6902 19.0402 85.0469 20.4446C85.3739 19.2324 85.7901 17.8132 86.2805 16.2019Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+4 -2
View File
@@ -37,7 +37,7 @@ export function Container({
return (
<div
ref={ref}
className="w-full bg-white text-black hover:bg-neutral-50 dark:bg-dimmedBg dark:hover:bg-dimmedBgLighten relative transition-colors max-w-[600px] min-w-[300px] flex border dark:border-slate-600 dark:text-slate-200 rounded-xl"
className="w-full bg-brand text-black dark:bg-brand relative transition-colors max-w-[600px] min-w-[300px] flex items-center dark:text-slate-200 rounded-[20px] cursor-pointer hover:bg-opacity-90"
onClick={() => {
if (ref.current && href) {
// forwardRef requires preact/compat - let's keep it simple
@@ -49,7 +49,9 @@ export function Container({
}
}}>
{href && <Link href={href} />}
<div className="flex-1 px-4 pt-3 pb-2.5 max-w-full">{children}</div>
<div className="flex-1 px-[6px] pt-[6px] pb-2.5 max-w-full">
{children}
</div>
</div>
)
}
+3 -3
View File
@@ -83,9 +83,9 @@ export function Embed({
return (
<Link
href={`/profile/${record.author.did}/post/${getRkey(record)}`}
className="transition-colors hover:bg-neutral-100 dark:hover:bg-slate-700 border dark:border-slate-600 rounded-xl p-2 gap-1.5 w-full flex flex-col">
className="transition-colors hover:bg-blue-50 dark:hover:bg-slate-900 border dark:border-slate-600 rounded-xl p-2 gap-1.5 w-full flex flex-col">
<div className="flex gap-1.5 items-center">
<div className="w-4 h-4 rounded-full bg-neutral-300 dark:bg-slate-700 shrink-0">
<div className="w-4 h-4 rounded-full bg-neutral-300 dark:bg-slate-900 shrink-0">
<img
className="rounded-full"
src={record.author.avatar}
@@ -222,7 +222,7 @@ export function Embed({
function Info({children}: {children: ComponentChildren}) {
return (
<div className="w-full rounded-xl border py-2 px-2.5 flex-row flex gap-2 bg-neutral-50">
<div className="w-full rounded-xl border py-2 px-2.5 flex-row flex gap-2 hover:bg-blue-50 dark:border-slate-600 dark:hover:bg-slate-900">
<img src={infoIcon} className="w-4 h-4 shrink-0 mt-0.5" />
<p className="text-sm text-textLight dark:text-textDimmed">{children}</p>
</div>
+63 -51
View File
@@ -6,10 +6,10 @@ import {
} from '@atproto/api'
import {h} from 'preact'
import replyIcon from '../../assets/bubble_filled_stroke2_corner2_rounded.svg'
import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg'
import logo from '../../assets/logo_full_name.svg'
import {Like as LikeIcon} from '../icons/Like'
import {Reply as ReplyIcon} from '../icons/Reply'
import {Repost as RepostIcon} from '../icons/Repost'
import {CONTENT_LABELS} from '../labels'
import * as bsky from '../types/bsky'
import {niceDate} from '../util/nice-date'
@@ -45,10 +45,13 @@ export function Post({thread}: Props) {
const verification = getVerificationState({profile: post.author})
const href = `/profile/${post.author.did}/post/${getRkey(post)}`
return (
<Container href={href}>
<div className="flex-1 flex-col flex gap-2" lang={record?.langs?.[0]}>
<div className="flex gap-2.5 items-center cursor-pointer w-full max-w-full">
<div
className="flex-1 flex-col flex gap-2 bg-neutral-50 dark:bg-black dark:hover:bg-slate-900 hover:bg-blue-50 rounded-[14px] p-4"
lang={record?.langs?.[0]}>
<div className="flex gap-2.5 items-center cursor-pointer w-full max-w-full ">
<Link
href={`/profile/${post.author.did}`}
className="rounded-full shrink-0">
@@ -80,57 +83,66 @@ export function Post({thread}: Props) {
@{post.author.handle}
</Link>
</div>
<Link
href={href}
className="transition-transform hover:scale-110 shrink-0 self-start">
<img src={logo} className="h-8" />
</Link>
</div>
<PostContent record={record} />
<Embed content={post.embed} labels={post.labels} />
<Link href={href}>
<time
datetime={new Date(post.indexedAt).toISOString()}
className="text-textLight dark:text-textDimmed mt-1 text-sm hover:underline">
{niceDate(post.indexedAt)}
</time>
</Link>
<div className="border-t dark:border-slate-600 w-full pt-2.5 flex items-center gap-5 text-sm cursor-pointer">
{!!post.likeCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={likeIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
{prettyNumber(post.likeCount)}
</p>
</div>
)}
{!!post.repostCount && (
<div className="flex items-center gap-2 cursor-pointer">
<img src={repostIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
{prettyNumber(post.repostCount)}
</p>
</div>
)}
<div className="flex items-center gap-2 cursor-pointer">
<img src={replyIcon} className="w-5 h-5" />
<p className="font-bold text-neutral-500 dark:text-neutral-300 mb-px">
Reply
</p>
<div className="flex items-center justify-between w-full pt-2.5 text-sm">
<div className="flex items-center gap-3 text-sm cursor-pointer">
{!!post.likeCount && (
<div className="flex items-center gap-1 cursor-pointer group">
<LikeIcon
width={20}
height={20}
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
/>
<p className="font-medium text-slate-600 text-neutral-600 dark:text-neutral-300 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors dark:text-slate-400">
{prettyNumber(post.likeCount)}
</p>
</div>
)}
{!!post.replyCount && (
<div className="flex items-center gap-1 cursor-pointer group">
<ReplyIcon
width={20}
height={20}
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
/>
<p className="font-medium text-slate-600 text-neutral-600 dark:text-neutral-300 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors dark:text-slate-400">
{prettyNumber(post.replyCount)}
</p>
</div>
)}
{!!post.repostCount && (
<div className="flex items-center gap-1 cursor-pointer group">
<RepostIcon
width={20}
height={20}
className="text-slate-600 dark:text-slate-400 group-hover:text-neutral-800 dark:group-hover:text-white transition-colors"
/>
<p className="font-medium text-slate-600 dark:text-slate-400 mb-px group-hover:text-neutral-800 dark:group-hover:text-white transition-colors">
{prettyNumber(post.repostCount)}
</p>
</div>
)}
</div>
<div className="flex-1" />
<p className="cursor-pointer text-brand dark:text-brandLighten font-bold hover:underline hidden min-[450px]:inline">
{post.replyCount
? `Read ${prettyNumber(post.replyCount)} ${
post.replyCount > 1 ? 'replies' : 'reply'
} on Bluesky`
: `View on Bluesky`}
</p>
<p className="cursor-pointer text-brand font-bold hover:underline min-[450px]:hidden">
<span className="hidden min-[380px]:inline">View on </span>Bluesky
</p>
<Link href={href}>
<time
datetime={new Date(post.indexedAt).toISOString()}
className="text-slate-500 dark:text-textDimmed text-sm hover:underline dark:text-slate-500">
{niceDate(post.indexedAt)}
</time>
</Link>
</div>
</div>
<div className="flex items-center justify-end pt-2">
<Link
href={href}
className="transition-transform hover:scale-110 shrink-0">
<img src={logo} className="h-8" />
</Link>
</div>
</Container>
)
}
+28
View File
@@ -0,0 +1,28 @@
import {h} from 'preact'
export const Like = ({
width = 16,
height = 17,
fill = 'currentColor',
className,
}: {
width?: number
height?: number
fill?: string
className?: string
}) => (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.1561 3.62664C10.3307 3.44261 9.35086 3.65762 8.47486 4.54615C8.34958 4.67323 8.17857 4.74478 8.00012 4.74478C7.82167 4.74478 7.65066 4.67324 7.52538 4.54616C6.64938 3.65762 5.66955 3.44261 4.84416 3.62664C4.0022 3.81438 3.25812 4.43047 2.89709 5.33069C2.21997 7.01907 2.83524 10.1257 8.00015 13.1315C13.165 10.1257 13.7803 7.01906 13.1032 5.33069C12.7421 4.43047 11.998 3.81437 11.1561 3.62664ZM14.3407 4.83438C15.4101 7.50098 14.0114 11.2942 8.32611 14.4808C8.12362 14.5943 7.87668 14.5943 7.6742 14.4808C1.98891 11.2942 0.590133 7.501 1.65956 4.83439C2.1788 3.53968 3.26862 2.61187 4.55399 2.32527C5.68567 2.07294 6.92237 2.32723 8.00012 3.18278C9.07786 2.32723 10.3146 2.07294 11.4462 2.32526C12.7316 2.61186 13.8214 3.53967 14.3407 4.83438Z"
fill={fill}
/>
</svg>
)
+28
View File
@@ -0,0 +1,28 @@
import {h} from 'preact'
export const Reply = ({
width = 16,
height = 17,
fill = 'currentColor',
className,
}: {
width?: number
height?: number
fill?: string
className?: string
}) => (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.3335 4.23242C1.3335 3.12785 2.22893 2.23242 3.3335 2.23242H12.6668C13.7714 2.23242 14.6668 3.12785 14.6668 4.23242V10.8991C14.6668 12.0037 13.7714 12.8991 12.6668 12.8991H8.18482L5.00983 14.8041C4.80387 14.9277 4.54737 14.9309 4.33836 14.8126C4.12936 14.6942 4.00016 14.4726 4.00016 14.2324V12.8991H3.3335C2.22893 12.8991 1.3335 12.0037 1.3335 10.8991V4.23242ZM3.3335 3.56576C2.96531 3.56576 2.66683 3.86423 2.66683 4.23242V10.8991C2.66683 11.2673 2.96531 11.5658 3.3335 11.5658H4.66683C5.03502 11.5658 5.3335 11.8642 5.3335 12.2324V13.055L7.65717 11.6608C7.76078 11.5986 7.87933 11.5658 8.00016 11.5658H12.6668C13.035 11.5658 13.3335 11.2673 13.3335 10.8991V4.23242C13.3335 3.86423 13.035 3.56576 12.6668 3.56576H3.3335Z"
fill={fill}
/>
</svg>
)
+26
View File
@@ -0,0 +1,26 @@
import {h} from 'preact'
export const Repost = ({
width = 16,
height = 17,
fill = 'currentColor',
className,
}: {
width?: number
height?: number
fill?: string
className?: string
}) => (
<svg
className={className}
width={width}
height={height}
viewBox="0 0 16 17"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.86204 9.76164C4.12239 9.50134 4.54442 9.50131 4.80475 9.76164C5.06503 10.022 5.06503 10.444 4.80475 10.7044L3.94277 11.5663H11.3334C12.0697 11.5663 12.6667 10.9693 12.6667 10.233V8.89966C12.6667 8.53147 12.9652 8.233 13.3334 8.233C13.7015 8.23305 14.0001 8.53151 14.0001 8.89966V10.233C14.0001 11.7057 12.8061 12.8996 11.3334 12.8997H3.94277L4.80475 13.7616C5.06503 14.022 5.06503 14.444 4.80475 14.7044C4.54442 14.9647 4.12239 14.9646 3.86204 14.7044L2.3334 13.1757C1.8127 12.655 1.8127 11.811 2.3334 11.2903L3.86204 9.76164ZM2.00006 7.56633V6.233C2.00006 4.76024 3.19397 3.56633 4.66673 3.56633H12.0574L11.1954 2.70435C10.935 2.444 10.935 2.02199 11.1954 1.76164C11.4557 1.50134 11.8778 1.50131 12.1381 1.76164L13.6667 3.29029C14.1873 3.81096 14.1873 4.65503 13.6667 5.17571L12.1381 6.70435C11.8778 6.96468 11.4557 6.96465 11.1954 6.70435C10.935 6.444 10.935 6.02199 11.1954 5.76164L12.0574 4.89966H4.66673C3.93035 4.89966 3.3334 5.49662 3.3334 6.233V7.56633C3.3334 7.93449 3.03487 8.23294 2.66673 8.233C2.29854 8.233 2.00006 7.93452 2.00006 7.56633Z"
fill={fill}
/>
</svg>
)
+1 -1
View File
@@ -223,7 +223,7 @@ func serve(cctx *cli.Context) error {
e.GET("/robots.txt", echo.WrapHandler(staticHandler))
}
e.GET("/iframe/youtube.html", echo.WrapHandler(staticHandler))
e.GET("/iframe/*", echo.WrapHandler(staticHandler))
e.GET("/static/*", echo.WrapHandler(http.StripPrefix("/static/", staticHandler)), func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Before(func() {
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/bluesky-social/social-app/bskyweb
go 1.24.5
go 1.25
require (
github.com/bluesky-social/indigo v0.0.0-20250729223159-573ae927246a
+1 -37
View File
@@ -16,40 +16,4 @@
}
</style>
<div class="container"><div class="video" id="player"></div></div>
<script>
const url = new URL(window.location)
const viewport = document.querySelector("meta[name=viewport]")
const tag = document.createElement("script")
tag.src = "https://www.youtube.com/iframe_api"
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
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,
playerVars: {
autoplay: 1,
start,
rel: 0,
loop: 0,
playsinline: 1,
origin: url.origin
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
</script>
<script src="youtube.js"></script>
+35
View File
@@ -0,0 +1,35 @@
const url = new URL(window.location)
const viewport = document.querySelector("meta[name=viewport]")
const tag = document.createElement("script")
tag.src = "https://www.youtube.com/iframe_api"
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
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,
playerVars: {
autoplay: 1,
start,
rel: 0,
loop: 0,
playsinline: 1,
origin: url.origin
},
});
}
function onPlayerReady(event) {
event.target.playVideo();
}
@@ -243,13 +243,18 @@ class BottomSheetView(
val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
bottomSheet?.let {
val behavior = BottomSheetBehavior.from(it)
val currentState = behavior.state
behavior.halfExpandedRatio = getHalfExpandedRatio(contentHeight)
val oldRatio = behavior.halfExpandedRatio
var newRatio = getHalfExpandedRatio(contentHeight)
behavior.halfExpandedRatio = newRatio
if (contentHeight > this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_EXPANDED) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
} else if (contentHeight < this.safeScreenHeight && behavior.state != BottomSheetBehavior.STATE_HALF_EXPANDED) {
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
} else if (currentState == BottomSheetBehavior.STATE_HALF_EXPANDED && oldRatio != newRatio) {
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
import {BottomSheet} from './src/BottomSheet'
import {
BottomSheetSnapPoint,
BottomSheetState,
BottomSheetViewProps,
type BottomSheetState,
type BottomSheetViewProps,
} from './src/BottomSheet.types'
import {BottomSheetNativeComponent} from './src/BottomSheetNativeComponent'
import {
@@ -112,9 +112,21 @@ export class BottomSheetNativeComponent extends React.Component<
onStateChange={this.onStateChange}
extraStyles={extraStyles}
onLayout={e => {
const {height} = e.nativeEvent.layout
this.setState({viewHeight: height})
this.updateLayout()
if (isIOS15) {
const {height} = e.nativeEvent.layout
this.setState({viewHeight: height})
}
if (Platform.OS === 'android') {
// TEMP HACKFIX: I had to timebox this, but this is Bad.
// On Android, if you run updateLayout() immediately,
// it will take ages to actually run on the native side.
// However, adding literally any delay will fix this, including
// a console.log() - just sending the log to the CLI is enough.
// TODO: Get to the bottom of this and fix it properly! -sfn
setTimeout(() => this.updateLayout())
} else {
this.updateLayout()
}
}}
/>
</Portal>
@@ -23,6 +23,10 @@ class ExpoReceiveAndroidIntentsModule : Module() {
ModuleDefinition {
Name("ExpoReceiveAndroidIntents")
OnCreate {
handleIntent(appContext.currentActivity?.intent)
}
OnNewIntent {
handleIntent(it)
}
@@ -74,10 +78,12 @@ class ExpoReceiveAndroidIntentsModule : Module() {
intent.getParcelableExtra(Intent.EXTRA_STREAM)
}
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
uri?.let {
when (type) {
AttachmentType.IMAGE -> handleImageIntents(listOf(it))
AttachmentType.VIDEO -> handleVideoIntents(listOf(it))
AttachmentType.IMAGE -> handleImageIntents(listOf(it), text)
AttachmentType.VIDEO -> handleVideoIntents(listOf(it), text)
}
}
}
@@ -99,15 +105,20 @@ class ExpoReceiveAndroidIntentsModule : Module() {
?.take(4)
}
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
uris?.let {
when (type) {
AttachmentType.IMAGE -> handleImageIntents(it)
AttachmentType.IMAGE -> handleImageIntents(it, text)
else -> return
}
}
}
private fun handleImageIntents(uris: List<Uri>) {
private fun handleImageIntents(
uris: List<Uri>,
text: String?
) {
var allParams = ""
uris.forEachIndexed { index, uri ->
@@ -120,15 +131,22 @@ class ExpoReceiveAndroidIntentsModule : Module() {
}
}
val encoded = URLEncoder.encode(allParams, "UTF-8")
val encodedUris = URLEncoder.encode(allParams, "UTF-8")
val encodedText = text?.let { URLEncoder.encode(it, "UTF-8") }
"bluesky://intent/compose?imageUris=$encoded".toUri().let {
var composeIntent = "bluesky://intent/compose?imageUris=$encodedUris"
encodedText?.let { composeIntent += "&text=$it" }
composeIntent.toUri().let {
val newIntent = Intent(Intent.ACTION_VIEW, it)
appContext.currentActivity?.startActivity(newIntent)
}
}
private fun handleVideoIntents(uris: List<Uri>) {
private fun handleVideoIntents(
uris: List<Uri>,
text: String?
) {
val uri = uris[0]
// If there is no extension for the file, substringAfterLast returns the original string - not
// null, so we check for that below
@@ -147,7 +165,12 @@ class ExpoReceiveAndroidIntentsModule : Module() {
val info = getVideoInfo(uri) ?: return
"bluesky://intent/compose?videoUri=${URLEncoder.encode(file.path, "UTF-8")}|${info["width"]}|${info["height"]}".toUri().let {
val encodedText = text?.let { URLEncoder.encode(it, "UTF-8") }
var composeIntent = "bluesky://intent/compose?videoUri=${URLEncoder.encode(file.path, "UTF-8")}|${info["width"]}|${info["height"]}"
encodedText?.let { composeIntent += "&text=$it" }
composeIntent.toUri().let {
val newIntent = Intent(Intent.ACTION_VIEW, it)
appContext.currentActivity?.startActivity(newIntent)
}
+26 -25
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.109.0",
"version": "1.111.0",
"private": true,
"engines": {
"node": ">=20"
@@ -72,7 +72,7 @@
"icons:optimize": "svgo -f ./assets/icons"
},
"dependencies": {
"@atproto/api": "^0.17.1",
"@atproto/api": "^0.18.0",
"@bitdrift/react-native": "^0.6.8",
"@braintree/sanitize-url": "^6.0.2",
"@bsky.app/alf": "^0.1.5",
@@ -131,19 +131,19 @@
"emoji-mart": "^5.5.2",
"emoji-regex": "^10.4.0",
"eventemitter3": "^5.0.1",
"expo": "^54.0.9",
"expo": "^54.0.22",
"expo-application": "~7.0.7",
"expo-blur": "~15.0.7",
"expo-build-properties": "~1.0.9",
"expo-camera": "~17.0.8",
"expo-camera": "~17.0.9",
"expo-clipboard": "~8.0.7",
"expo-dev-client": "~6.0.12",
"expo-device": "~8.0.8",
"expo-file-system": "~19.0.14",
"expo-font": "~14.0.8",
"expo-dev-client": "~6.0.16",
"expo-device": "~8.0.9",
"expo-file-system": "~19.0.17",
"expo-font": "~14.0.9",
"expo-haptics": "~15.0.7",
"expo-image": "~3.0.8",
"expo-image-crop-tool": "^0.1.8",
"expo-image": "~3.0.10",
"expo-image-crop-tool": "^0.4.0",
"expo-image-manipulator": "~14.0.7",
"expo-image-picker": "~17.0.8",
"expo-intent-launcher": "~13.0.7",
@@ -153,15 +153,15 @@
"expo-localization": "~17.0.7",
"expo-location": "~19.0.7",
"expo-media-library": "~18.2.0",
"expo-notifications": "~0.32.11",
"expo-notifications": "~0.32.12",
"expo-screen-orientation": "~9.0.7",
"expo-sharing": "~14.0.7",
"expo-splash-screen": "~31.0.10",
"expo-system-ui": "~6.0.7",
"expo-task-manager": "~14.0.7",
"expo-updates": "~29.0.11",
"expo-video": "~3.0.11",
"expo-web-browser": "~15.0.7",
"expo-system-ui": "~6.0.8",
"expo-task-manager": "~14.0.8",
"expo-updates": "~29.0.12",
"expo-video": "~3.0.13",
"expo-web-browser": "~15.0.9",
"fast-text-encoding": "^1.0.6",
"history": "^5.3.0",
"hls.js": "^1.6.2",
@@ -173,10 +173,11 @@
"lodash.isequal": "^4.5.0",
"lodash.shuffle": "^4.2.0",
"lodash.throttle": "^4.1.1",
"mediabunny": "^1.25.3",
"multiformats": "9.9.0",
"nanoid": "^5.0.5",
"normalize-url": "^8.0.0",
"patch-package": "^6.5.1",
"patch-package": "^8.0.1",
"postinstall-postinstall": "^2.1.0",
"psl": "^1.9.0",
"radix-ui": "^1.4.3",
@@ -186,7 +187,7 @@
"react-image-crop": "^11.0.7",
"react-is": "19",
"react-keyed-flatten-children": "^5.0.0",
"react-native": "0.81.4",
"react-native": "0.81.5",
"react-native-compressor": "^1.13.0",
"react-native-date-picker": "^5.0.13",
"react-native-device-attest": "^0.1.6",
@@ -229,13 +230,13 @@
"@lingui/cli": "^4.14.1",
"@lingui/macro": "^4.14.1",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@react-native/babel-preset": "0.81.4",
"@react-native/eslint-config": "^0.81.4",
"@react-native/typescript-config": "^0.81.4",
"@react-native/babel-preset": "0.81.5",
"@react-native/eslint-config": "^0.81.5",
"@react-native/typescript-config": "^0.81.5",
"@sentry/webpack-plugin": "^3.2.2",
"@testing-library/jest-native": "^5.4.3",
"@testing-library/react-native": "^13.2.0",
"@types/jest": "^29.4.0",
"@types/jest": "29.5.14",
"@types/lodash.chunk": "^4.2.7",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.isequal": "^4.5.6",
@@ -263,7 +264,7 @@
"husky": "^8.0.3",
"is-ci": "^3.0.1",
"jest": "^29.7.0",
"jest-expo": "~54.0.12",
"jest-expo": "~54.0.13",
"jest-junit": "^16.0.0",
"lint-staged": "^13.2.3",
"lockfile-lint": "^4.14.0",
@@ -278,8 +279,8 @@
"webpack-bundle-analyzer": "^4.10.1"
},
"resolutions": {
"@react-native/babel-preset": "0.81.4",
"@react-native/normalize-colors": "0.81.4",
"@react-native/babel-preset": "0.81.5",
"@react-native/normalize-colors": "0.81.5",
"**/@expo/image-utils": "0.8.7",
"**/expo-constants": "18.0.8",
"**/expo-device": "7.1.4",
@@ -1,5 +1,26 @@
diff --git a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt
index e9a8e3d..3c684e0 100644
--- a/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt
+++ b/node_modules/expo-updates/android/src/main/java/expo/modules/updates/loader/LoaderTask.kt
@@ -296,14 +296,13 @@ class LoaderTask(
) {
try {
val embeddedLoader = EmbeddedLoader(context, configuration, logger, database, directory)
- val result = embeddedLoader.load { updateResponse ->
+ embeddedLoader.load { _ ->
Loader.OnUpdateResponseLoadedResult(shouldDownloadManifestIfPresentInResponse = true)
}
- launcher.launch(database)
} catch (e: Exception) {
logger.error("Unexpected error copying embedded update", e, UpdatesErrorCode.Unknown)
- launcher.launch(database)
}
+ launcher.launch(database)
} else {
launcher.launch(database)
}
diff --git a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
index b85291e..546709d 100644
index 68086bd..78c7761 100644
--- a/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
+++ b/node_modules/expo-updates/ios/EXUpdates/Update/ExpoUpdatesUpdate.swift
@@ -78,13 +78,20 @@ public final class ExpoUpdatesUpdate: Update {
@@ -1,47 +1,16 @@
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
index 914a249..0deac55 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
@@ -19,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface RCTPullToRefreshViewComponentView : RCTViewComponentView <RCTCustomPullToRefreshViewProtocol>
+- (void)beginRefreshingProgrammatically;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
index d029337..0f63ea3 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
@@ -1038,6 +1038,11 @@ - (void)_adjustForMaintainVisibleContentPosition
}
}
++ (BOOL)shouldBeRecycled
+{
+ return NO;
+}
+
@end
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
index e9b330f..ec5f58c 100644
index e9b330f..5fbb2e0 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
@@ -15,5 +15,8 @@
@@ -15,5 +15,6 @@
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) RCTDirectEventBlock onRefresh;
@property (nonatomic, weak) UIScrollView *scrollView;
+@property (nonatomic, copy) UIColor *customTintColor;
+
+- (void)forwarderBeginRefreshing;
@end
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
index 53bfd04..ff1b1ed 100644
index 53bfd04..e2e0c9f 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
@@ -23,6 +23,7 @@ @implementation RCTRefreshControl {
@@ -65,7 +34,7 @@ index 53bfd04..ff1b1ed 100644
- (void)didMoveToWindow
{
[super didMoveToWindow];
@@ -221,4 +228,50 @@ - (void)refreshControlValueChanged
@@ -221,4 +228,16 @@ - (void)refreshControlValueChanged
}
}
@@ -80,40 +49,6 @@ index 53bfd04..ff1b1ed 100644
+ [super setTintColor:tintColor];
+ }
+}
+
+// This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
+// libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
+// function.
+- (void)forwarderBeginRefreshing
+{
+ _refreshingProgrammatically = NO;
+
+ [self sizeToFit];
+
+ if (!self.scrollView) {
+ return;
+ }
+
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
+
+ [UIView animateWithDuration:0.3
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^(void) {
+ // Whenever we call this method, the scrollview will always be at a position of
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
+ [scrollView setContentOffset:CGPointMake(0, -65)];
+ }
+ completion:^(__unused BOOL finished) {
+ [super beginRefreshing];
+ [self setCurrentRefreshingState:super.refreshing];
+
+ if (self->_onRefresh) {
+ self->_onRefresh(nil);
+ }
+ }
+ ];
+}
+
@end
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControlManager.m
@@ -0,0 +1,67 @@
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
index 914a249..0deac55 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTPullToRefreshViewComponentView.h
@@ -19,6 +19,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface RCTPullToRefreshViewComponentView : RCTViewComponentView <RCTCustomPullToRefreshViewProtocol>
+- (void)beginRefreshingProgrammatically;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
index 5fbb2e0..ec5f58c 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.h
@@ -17,4 +17,6 @@
@property (nonatomic, weak) UIScrollView *scrollView;
@property (nonatomic, copy) UIColor *customTintColor;
+- (void)forwarderBeginRefreshing;
+
@end
diff --git a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
index e2e0c9f..ff1b1ed 100644
--- a/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
+++ b/node_modules/react-native/React/Views/RefreshControl/RCTRefreshControl.m
@@ -240,4 +240,38 @@ - (void)setTintColor:(UIColor *)tintColor
}
}
+// This method is used by Bluesky's ExpoScrollForwarder. This allows other React Native
+// libraries to perform a refresh of a scrollview and access the refresh control's onRefresh
+// function.
+- (void)forwarderBeginRefreshing
+{
+ _refreshingProgrammatically = NO;
+
+ [self sizeToFit];
+
+ if (!self.scrollView) {
+ return;
+ }
+
+ UIScrollView *scrollView = (UIScrollView *)self.scrollView;
+
+ [UIView animateWithDuration:0.3
+ delay:0
+ options:UIViewAnimationOptionBeginFromCurrentState
+ animations:^(void) {
+ // Whenever we call this method, the scrollview will always be at a position of
+ // -130 or less. Scrolling back to -65 simulates the default behavior of RCTRefreshControl
+ [scrollView setContentOffset:CGPointMake(0, -65)];
+ }
+ completion:^(__unused BOOL finished) {
+ [super beginRefreshing];
+ [self setCurrentRefreshingState:super.refreshing];
+
+ if (self->_onRefresh) {
+ self->_onRefresh(nil);
+ }
+ }
+ ];
+}
+
@end
@@ -0,0 +1,16 @@
diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
index d029337..0f63ea3 100644
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
@@ -1038,6 +1038,11 @@ - (void)_adjustForMaintainVisibleContentPosition
}
}
++ (BOOL)shouldBeRecycled
+{
+ return NO;
+}
+
@end
Class<RCTComponentViewProtocol> RCTScrollViewCls(void)
@@ -0,0 +1,389 @@
diff --git a/node_modules/react-native-reanimated/lib/module/component/PerformanceMonitor.js b/node_modules/react-native-reanimated/lib/module/component/PerformanceMonitor.js
index 9c98d6c..70ada9f 100644
--- a/node_modules/react-native-reanimated/lib/module/component/PerformanceMonitor.js
+++ b/node_modules/react-native-reanimated/lib/module/component/PerformanceMonitor.js
@@ -1,125 +1,5 @@
'use strict';
-import React, { useEffect, useRef } from 'react';
-import { StyleSheet, TextInput, View } from 'react-native';
-import { addWhitelistedNativeProps } from "../ConfigHelper.js";
-import { createAnimatedComponent } from "../createAnimatedComponent/index.js";
-import { useAnimatedProps, useFrameCallback, useSharedValue } from "../hook/index.js";
-function createCircularDoublesBuffer(size) {
- 'worklet';
-
- return {
- next: 0,
- buffer: new Float32Array(size),
- size,
- count: 0,
- push(value) {
- const oldValue = this.buffer[this.next];
- const oldCount = this.count;
- this.buffer[this.next] = value;
- this.next = (this.next + 1) % this.size;
- this.count = Math.min(this.size, this.count + 1);
- return oldCount === this.size ? oldValue : null;
- },
- front() {
- const notEmpty = this.count > 0;
- if (notEmpty) {
- const current = this.next - 1;
- const index = current < 0 ? this.size - 1 : current;
- return this.buffer[index];
- }
- return null;
- },
- back() {
- const notEmpty = this.count > 0;
- return notEmpty ? this.buffer[this.next] : null;
- }
- };
-}
-const DEFAULT_BUFFER_SIZE = 20;
-addWhitelistedNativeProps({
- text: true
-});
-const AnimatedTextInput = createAnimatedComponent(TextInput);
-function loopAnimationFrame(fn) {
- let lastTime = 0;
- function loop() {
- requestAnimationFrame(time => {
- if (lastTime > 0) {
- fn(lastTime, time);
- }
- lastTime = time;
- requestAnimationFrame(loop);
- });
- }
- loop();
-}
-function getFps(renderTimeInMs) {
- 'worklet';
-
- return 1000 / renderTimeInMs;
-}
-function completeBufferRoutine(buffer, timestamp) {
- 'worklet';
-
- timestamp = Math.round(timestamp);
- const droppedTimestamp = buffer.push(timestamp) ?? timestamp;
- const measuredRangeDuration = timestamp - droppedTimestamp;
- return getFps(measuredRangeDuration / buffer.count);
-}
-function JsPerformance({
- smoothingFrames
-}) {
- const jsFps = useSharedValue(null);
- const totalRenderTime = useSharedValue(0);
- const circularBuffer = useRef(createCircularDoublesBuffer(smoothingFrames));
- useEffect(() => {
- loopAnimationFrame((_, timestamp) => {
- timestamp = Math.round(timestamp);
- const currentFps = completeBufferRoutine(circularBuffer.current, timestamp);
-
- // JS fps have to be measured every 2nd frame,
- // thus 2x multiplication has to occur here
- jsFps.value = (currentFps * 2).toFixed(0);
- });
- }, [jsFps, totalRenderTime]);
- const animatedProps = useAnimatedProps(() => {
- const text = 'JS: ' + (jsFps.value ?? 'N/A') + ' ';
- return {
- text,
- defaultValue: text
- };
- });
- return <View style={styles.container}>
- <AnimatedTextInput style={styles.text} animatedProps={animatedProps} editable={false} />
- </View>;
-}
-function UiPerformance({
- smoothingFrames
-}) {
- const uiFps = useSharedValue(null);
- const circularBuffer = useSharedValue(null);
- useFrameCallback(({
- timestamp
- }) => {
- if (circularBuffer.value === null) {
- circularBuffer.value = createCircularDoublesBuffer(smoothingFrames);
- }
- timestamp = Math.round(timestamp);
- const currentFps = completeBufferRoutine(circularBuffer.value, timestamp);
- uiFps.value = currentFps.toFixed(0);
- });
- const animatedProps = useAnimatedProps(() => {
- const text = 'UI: ' + (uiFps.value ?? 'N/A') + ' ';
- return {
- text,
- defaultValue: text
- };
- });
- return <View style={styles.container}>
- <AnimatedTextInput style={styles.text} animatedProps={animatedProps} editable={false} />
- </View>;
-}
/**
* A component that lets you measure fps values on JS and UI threads on both the
* Paper and Fabric architectures.
@@ -127,38 +7,7 @@ function UiPerformance({
* @param smoothingFrames - Determines amount of saved frames which will be used
* for fps value smoothing.
*/
-export function PerformanceMonitor({
- smoothingFrames = DEFAULT_BUFFER_SIZE
-}) {
- return <View style={styles.monitor}>
- <JsPerformance smoothingFrames={smoothingFrames} />
- <UiPerformance smoothingFrames={smoothingFrames} />
- </View>;
+export function PerformanceMonitor() {
+ return null;
}
-const styles = StyleSheet.create({
- monitor: {
- flexDirection: 'row',
- position: 'absolute',
- backgroundColor: '#0006',
- zIndex: 1000
- },
- header: {
- fontSize: 14,
- color: '#ffff',
- paddingHorizontal: 5
- },
- text: {
- fontSize: 13,
- fontVariant: ['tabular-nums'],
- color: '#ffff',
- fontFamily: 'monospace',
- paddingHorizontal: 3
- },
- container: {
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'row',
- flexWrap: 'wrap'
- }
-});
//# sourceMappingURL=PerformanceMonitor.js.map
diff --git a/node_modules/react-native-reanimated/src/component/PerformanceMonitor.tsx b/node_modules/react-native-reanimated/src/component/PerformanceMonitor.tsx
index ff8fc8a..34dde79 100644
--- a/node_modules/react-native-reanimated/src/component/PerformanceMonitor.tsx
+++ b/node_modules/react-native-reanimated/src/component/PerformanceMonitor.tsx
@@ -1,170 +1,5 @@
'use strict';
-import React, { useEffect, useRef } from 'react';
-import { StyleSheet, TextInput, View } from 'react-native';
-
-import { addWhitelistedNativeProps } from '../ConfigHelper';
-import { createAnimatedComponent } from '../createAnimatedComponent';
-import type { FrameInfo } from '../frameCallback';
-import { useAnimatedProps, useFrameCallback, useSharedValue } from '../hook';
-
-type CircularBuffer = ReturnType<typeof createCircularDoublesBuffer>;
-function createCircularDoublesBuffer(size: number) {
- 'worklet';
-
- return {
- next: 0 as number,
- buffer: new Float32Array(size),
- size,
- count: 0 as number,
-
- push(value: number): number | null {
- const oldValue = this.buffer[this.next];
- const oldCount = this.count;
- this.buffer[this.next] = value;
-
- this.next = (this.next + 1) % this.size;
- this.count = Math.min(this.size, this.count + 1);
- return oldCount === this.size ? oldValue : null;
- },
-
- front(): number | null {
- const notEmpty = this.count > 0;
- if (notEmpty) {
- const current = this.next - 1;
- const index = current < 0 ? this.size - 1 : current;
- return this.buffer[index];
- }
- return null;
- },
-
- back(): number | null {
- const notEmpty = this.count > 0;
- return notEmpty ? this.buffer[this.next] : null;
- },
- };
-}
-
-const DEFAULT_BUFFER_SIZE = 20;
-addWhitelistedNativeProps({ text: true });
-const AnimatedTextInput = createAnimatedComponent(TextInput);
-
-function loopAnimationFrame(fn: (lastTime: number, time: number) => void) {
- let lastTime = 0;
-
- function loop() {
- requestAnimationFrame((time) => {
- if (lastTime > 0) {
- fn(lastTime, time);
- }
- lastTime = time;
- requestAnimationFrame(loop);
- });
- }
-
- loop();
-}
-
-function getFps(renderTimeInMs: number): number {
- 'worklet';
- return 1000 / renderTimeInMs;
-}
-
-function completeBufferRoutine(
- buffer: CircularBuffer,
- timestamp: number
-): number {
- 'worklet';
- timestamp = Math.round(timestamp);
-
- const droppedTimestamp = buffer.push(timestamp) ?? timestamp;
-
- const measuredRangeDuration = timestamp - droppedTimestamp;
-
- return getFps(measuredRangeDuration / buffer.count);
-}
-
-function JsPerformance({ smoothingFrames }: { smoothingFrames: number }) {
- const jsFps = useSharedValue<string | null>(null);
- const totalRenderTime = useSharedValue(0);
- const circularBuffer = useRef<CircularBuffer>(
- createCircularDoublesBuffer(smoothingFrames)
- );
-
- useEffect(() => {
- loopAnimationFrame((_, timestamp) => {
- timestamp = Math.round(timestamp);
-
- const currentFps = completeBufferRoutine(
- circularBuffer.current,
- timestamp
- );
-
- // JS fps have to be measured every 2nd frame,
- // thus 2x multiplication has to occur here
- jsFps.value = (currentFps * 2).toFixed(0);
- });
- }, [jsFps, totalRenderTime]);
-
- const animatedProps = useAnimatedProps(() => {
- const text = 'JS: ' + (jsFps.value ?? 'N/A') + ' ';
- return { text, defaultValue: text };
- });
-
- return (
- <View style={styles.container}>
- <AnimatedTextInput
- style={styles.text}
- animatedProps={animatedProps}
- editable={false}
- />
- </View>
- );
-}
-
-function UiPerformance({ smoothingFrames }: { smoothingFrames: number }) {
- const uiFps = useSharedValue<string | null>(null);
- const circularBuffer = useSharedValue<CircularBuffer | null>(null);
-
- useFrameCallback(({ timestamp }: FrameInfo) => {
- if (circularBuffer.value === null) {
- circularBuffer.value = createCircularDoublesBuffer(smoothingFrames);
- }
-
- timestamp = Math.round(timestamp);
-
- const currentFps = completeBufferRoutine(circularBuffer.value, timestamp);
-
- uiFps.value = currentFps.toFixed(0);
- });
-
- const animatedProps = useAnimatedProps(() => {
- const text = 'UI: ' + (uiFps.value ?? 'N/A') + ' ';
- return { text, defaultValue: text };
- });
-
- return (
- <View style={styles.container}>
- <AnimatedTextInput
- style={styles.text}
- animatedProps={animatedProps}
- editable={false}
- />
- </View>
- );
-}
-
-export type PerformanceMonitorProps = {
- /**
- * Sets amount of previous frames used for smoothing at highest expectedFps.
- *
- * Automatically scales down at lower frame rates.
- *
- * Affects jumpiness of the FPS measurements value.
- */
- smoothingFrames?: number;
-};
-
/**
* A component that lets you measure fps values on JS and UI threads on both the
* Paper and Fabric architectures.
@@ -172,40 +7,6 @@ export type PerformanceMonitorProps = {
* @param smoothingFrames - Determines amount of saved frames which will be used
* for fps value smoothing.
*/
-export function PerformanceMonitor({
- smoothingFrames = DEFAULT_BUFFER_SIZE,
-}: PerformanceMonitorProps) {
- return (
- <View style={styles.monitor}>
- <JsPerformance smoothingFrames={smoothingFrames} />
- <UiPerformance smoothingFrames={smoothingFrames} />
- </View>
- );
+export function PerformanceMonitor() {
+ return null;
}
-
-const styles = StyleSheet.create({
- monitor: {
- flexDirection: 'row',
- position: 'absolute',
- backgroundColor: '#0006',
- zIndex: 1000,
- },
- header: {
- fontSize: 14,
- color: '#ffff',
- paddingHorizontal: 5,
- },
- text: {
- fontSize: 13,
- fontVariant: ['tabular-nums'],
- color: '#ffff',
- fontFamily: 'monospace',
- paddingHorizontal: 3,
- },
- container: {
- alignItems: 'center',
- justifyContent: 'center',
- flexDirection: 'row',
- flexWrap: 'wrap',
- },
-});
+2 -1
View File
@@ -25,7 +25,8 @@ export function normalizeTextStyles(
fontFamily: Alf['fonts']['family']
} & Pick<Alf, 'flags'>,
) {
const s = flatten(styles)
const s = flatten(styles) ?? {}
// should always be defined on these components
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
+58 -24
View File
@@ -5,18 +5,20 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useActorStatus} from '#/lib/actor-status'
import {isJwtExpired} from '#/lib/jwt'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useProfilesQuery} from '#/state/queries/profile'
import {type SessionAccount, useSession} from '#/state/session'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron'
import {Button} from '#/components/Button'
import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import {Text} from '#/components/Typography'
import {useSimpleVerificationState} from '#/components/verification'
import {VerificationCheck} from '#/components/verification/VerificationCheck'
import {Button} from './Button'
import {Text} from './Typography'
export function AccountList({
onSelectAccount,
@@ -44,9 +46,9 @@ export function AccountList({
<View
pointerEvents={pendingDid ? 'none' : 'auto'}
style={[
a.rounded_md,
a.rounded_lg,
a.overflow_hidden,
{borderWidth: 1},
a.border,
t.atoms.border_contrast_low,
]}>
{accounts.map(account => (
@@ -58,7 +60,7 @@ export function AccountList({
isCurrentAccount={account.did === currentAccount?.did}
isPendingAccount={account.did === pendingDid}
/>
<View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} />
<View style={[a.border_b, t.atoms.border_contrast_low]} />
</React.Fragment>
))}
<Button
@@ -72,22 +74,25 @@ export function AccountList({
a.flex_1,
a.flex_row,
a.align_center,
{height: 48},
a.p_lg,
a.gap_sm,
(hovered || pressed) && t.atoms.bg_contrast_25,
]}>
<Text
<View
style={[
a.font_semi_bold,
a.flex_1,
a.flex_row,
a.py_sm,
a.leading_tight,
t.atoms.text_contrast_medium,
{paddingLeft: 56},
t.atoms.bg_contrast_25,
a.rounded_full,
{width: 48, height: 48},
a.justify_center,
a.align_center,
(hovered || pressed) && t.atoms.bg_contrast_50,
]}>
<PlusIcon style={[t.atoms.text_contrast_low]} size="md" />
</View>
<Text style={[a.flex_1, a.leading_tight, a.text_md, a.font_medium]}>
{otherLabel ?? <Trans>Other account</Trans>}
</Text>
<Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
<ChevronIcon size="md" style={[t.atoms.text_contrast_low]} />
</View>
)}
</Button>
@@ -117,6 +122,8 @@ function AccountItem({
onSelect(account)
}, [account, onSelect])
const isLoggedOut = !account.refreshJwt || isJwtExpired(account.refreshJwt)
return (
<Button
testID={`chooseAccountBtn-${account.handle}`}
@@ -134,14 +141,13 @@ function AccountItem({
a.flex_1,
a.flex_row,
a.align_center,
a.px_md,
a.p_lg,
a.gap_sm,
{height: 56},
(hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25,
]}>
<UserAvatar
avatar={profile?.avatar}
size={36}
size={48}
type={profile?.associated?.labeler ? 'labeler' : 'user'}
live={live}
hideLiveBadge
@@ -151,7 +157,7 @@ function AccountItem({
<View style={[a.flex_row, a.align_center, a.gap_xs]}>
<Text
emoji
style={[a.font_semi_bold, a.leading_tight]}
style={[a.font_medium, a.leading_tight, a.text_md]}
numberOfLines={1}>
{sanitizeDisplayName(
profile?.displayName || profile?.handle || account.handle,
@@ -166,15 +172,43 @@ function AccountItem({
</View>
)}
</View>
<Text style={[a.leading_tight, t.atoms.text_contrast_medium]}>
<Text
style={[
a.leading_tight,
t.atoms.text_contrast_medium,
a.text_sm,
]}>
{sanitizeHandle(account.handle, '@')}
</Text>
{isLoggedOut && (
<Text
style={[
a.leading_tight,
a.text_xs,
a.italic,
t.atoms.text_contrast_medium,
]}>
<Trans>Logged out</Trans>
</Text>
)}
</View>
{isCurrentAccount ? (
<Check size="sm" style={[{color: t.palette.positive_500}]} />
<View
style={[
{
width: 20,
height: 20,
backgroundColor: t.palette.positive_500,
},
a.rounded_full,
a.justify_center,
a.align_center,
]}>
<CheckIcon size="xs" style={[{color: t.palette.white}]} />
</View>
) : (
<Chevron size="sm" style={[t.atoms.text]} />
<ChevronIcon size="md" style={[t.atoms.text_contrast_low]} />
)}
</View>
)}
+1
View File
@@ -48,6 +48,7 @@ export function AppLanguageDropdown() {
})}
variant="ghost"
color="secondary"
shape="rectangular"
style={[
a.pr_xs,
a.pl_sm,
+94 -50
View File
@@ -7,7 +7,6 @@ import {
Pressable,
type PressableProps,
type StyleProp,
StyleSheet,
type TargetedEvent,
type TextProps,
type TextStyle,
@@ -40,7 +39,7 @@ export type ButtonColor =
| 'primary_subtle'
| 'negative_subtle'
export type ButtonSize = 'tiny' | 'small' | 'large'
export type ButtonShape = 'round' | 'square' | 'default'
export type ButtonShape = 'round' | 'square' | 'rectangular' | 'default'
export type VariantProps = {
/**
* The style variation of the button
@@ -57,6 +56,11 @@ export type VariantProps = {
size?: ButtonSize
/**
* The shape of the button
*
* - `default`: Pill shaped. Most buttons should use this shape.
* - `round`: Circular. For icon-only buttons.
* - `square`: Square. For icon-only buttons.
* - `rectangular`: Rectangular. Matches previous style, use when adjacent to form fields.
*/
shape?: ButtonShape
}
@@ -435,6 +439,26 @@ export const Button = React.forwardRef<View, ButtonProps>(
}
if (shape === 'default') {
if (size === 'large') {
baseStyles.push(a.rounded_full, {
paddingVertical: 12,
paddingHorizontal: 24,
gap: 6,
})
} else if (size === 'small') {
baseStyles.push(a.rounded_full, {
paddingVertical: 8,
paddingHorizontal: 14,
gap: 5,
})
} else if (size === 'tiny') {
baseStyles.push(a.rounded_full, {
paddingVertical: 5,
paddingHorizontal: 10,
gap: 3,
})
}
} else if (shape === 'rectangular') {
if (size === 'large') {
baseStyles.push({
paddingVertical: 12,
@@ -507,13 +531,12 @@ export const Button = React.forwardRef<View, ButtonProps>(
variant,
color,
size,
shape,
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
[state, variant, color, size, shape, disabled],
)
const flattenedBaseStyles = flatten([baseStyles, style])
return (
<PressableComponent
role="button"
@@ -533,9 +556,10 @@ export const Button = React.forwardRef<View, ButtonProps>(
a.align_center,
a.justify_center,
a.curve_continuous,
flattenedBaseStyles,
baseStyles,
style,
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
? [hoverStyles, hoverStyleProp]
: []),
]}
onPressIn={onPressIn}
@@ -726,7 +750,7 @@ export function useSharedButtonTextStyles() {
baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold)
}
return StyleSheet.flatten(baseStyles)
return flatten(baseStyles)
}, [t, variant, color, size, disabled])
}
@@ -751,59 +775,79 @@ export function ButtonIcon({
position?: 'left' | 'right'
size?: SVGIconProps['size']
}) {
const {size: buttonSize} = useButtonContext()
const {size: buttonSize, shape: buttonShape} = useButtonContext()
const textStyles = useSharedButtonTextStyles()
const {iconSize, iconContainerSize} = React.useMemo(() => {
/**
* Pre-set icon sizes for different button sizes
*/
const iconSizeShorthand =
size ??
(({
large: 'md',
small: 'sm',
tiny: 'xs',
}[buttonSize || 'small'] || 'sm') as Exclude<
SVGIconProps['size'],
undefined
>)
const {iconSize, iconContainerSize, iconNegativeMargin} =
React.useMemo(() => {
/**
* Pre-set icon sizes for different button sizes
*/
const iconSizeShorthand =
size ??
(({
large: 'md',
small: 'sm',
tiny: 'xs',
}[buttonSize || 'small'] || 'sm') as Exclude<
SVGIconProps['size'],
undefined
>)
/*
* Copied here from icons/common.tsx so we can tweak if we need to, but
* also so that we can calculate transforms.
*/
const iconSize = {
xs: 12,
sm: 16,
md: 18,
lg: 24,
xl: 28,
'2xl': 32,
}[iconSizeShorthand]
/*
* Copied here from icons/common.tsx so we can tweak if we need to, but
* also so that we can calculate transforms.
*/
const iconSize = {
xs: 12,
sm: 16,
md: 18,
lg: 24,
xl: 28,
'2xs': 8,
'2xl': 32,
'3xl': 40,
}[iconSizeShorthand]
/*
* Goal here is to match rendered text size so that different size icons
* don't increase button size
*/
const iconContainerSize = {
large: 20,
small: 17,
tiny: 15,
}[buttonSize || 'small']
/*
* Goal here is to match rendered text size so that different size icons
* don't increase button size
*/
const iconContainerSize = {
large: 20,
small: 17,
tiny: 15,
}[buttonSize || 'small']
return {
iconSize,
iconContainerSize,
}
}, [buttonSize, size])
/*
* The icon needs to be closer to the edge of the button than the text. Therefore
* we make the gap slightly too large, and then pull in the sides using negative margins.
*/
let iconNegativeMargin = 0
if (buttonShape === 'default') {
iconNegativeMargin = {
large: -2,
small: -2,
tiny: -1,
}[buttonSize || 'small']
}
return {
iconSize,
iconContainerSize,
iconNegativeMargin,
}
}, [buttonSize, buttonShape, size])
return (
<View
style={[
a.z_20,
{
width: iconContainerSize,
width: size === '2xs' ? 10 : iconContainerSize,
height: iconContainerSize,
marginLeft: iconNegativeMargin,
marginRight: iconNegativeMargin,
},
]}>
<View
+74
View File
@@ -0,0 +1,74 @@
import {TouchableWithoutFeedback, View} from 'react-native'
import * as Clipboard from 'expo-clipboard'
import {atoms as a, useTheme} from '#/alf'
import * as Prompt from '#/components/Prompt'
import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography'
import {useDevMode} from '#/storage/hooks/dev-mode'
/**
* Internal-use component to display debug information supplied by the appview.
* The `debug` field only exists on some API views, and is only visible for
* internal users in dev mode. As such, none of these strings need to be
* translated.
*
* This component can be removed at any time if we don't find it useful.
*/
export function DebugFieldDisplay<T extends {debug?: {[x: string]: unknown}}>({
subject,
}: {
subject: T
}) {
const t = useTheme()
const [devMode] = useDevMode()
const prompt = Prompt.usePromptControl()
if (!devMode) return
if (!subject.debug) return
return (
<>
<Prompt.Basic
control={prompt}
title="Debug"
description={JSON.stringify(subject.debug, null, 2)}
cancelButtonCta="Close"
confirmButtonCta="Copy"
onConfirm={() => {
Clipboard.setStringAsync(JSON.stringify(subject.debug, null, 2))
Toast.show('Copied to clipboard', {type: 'success'})
}}
/>
<TouchableWithoutFeedback
accessibilityRole="button"
onPress={e => {
e.preventDefault()
e.stopPropagation()
prompt.open()
return false
}}>
<View style={[a.flex_row, a.align_center, a.gap_xs, a.pt_sm, a.pb_xs]}>
<View
style={[a.py_xs, a.px_sm, a.rounded_sm, t.atoms.bg_contrast_25]}>
<Text
style={[a.font_bold, a.text_xs, t.atoms.text_contrast_medium]}>
Debug
</Text>
</View>
<Text
numberOfLines={1}
style={[
a.flex_1,
a.text_xs,
a.leading_tight,
{fontFamily: 'monospace'},
t.atoms.text_contrast_low,
]}>
{JSON.stringify(subject.debug)}
</Text>
</View>
</TouchableWithoutFeedback>
</>
)
}
+21 -2
View File
@@ -13,6 +13,7 @@ import {
type DialogControlRefProps,
type DialogOuterProps,
} from '#/components/Dialog/types'
import {IS_DEV} from '#/env'
import {BottomSheetSnapPoint} from '../../../modules/bottom-sheet/src/BottomSheet.types'
export const Context = createContext<DialogContextProps>({
@@ -50,10 +51,28 @@ export function useDialogControl(): DialogOuterProps['control'] {
id,
ref: control,
open: () => {
control.current.open()
if (control.current) {
control.current.open()
} else {
if (IS_DEV) {
console.warn(
'Attemped to open a dialog control that was not attached to a dialog!\n' +
'Please ensure that the Dialog is mounted when calling open/close',
)
}
}
},
close: cb => {
control.current.close(cb)
if (control.current) {
control.current.close(cb)
} else {
if (IS_DEV) {
console.warn(
'Attemped to close a dialog control that was not attached to a dialog!\n' +
'Please ensure that the Dialog is mounted when calling open/close',
)
}
}
},
}),
[id, control],
+2 -1
View File
@@ -180,6 +180,7 @@ export function Inner({
onClick={stopPropagation}
onStartShouldSetResponder={_ => true}
onTouchEnd={stopPropagation}
// note: flatten is required for some reason -sfn
style={flatten([
a.relative,
a.rounded_md,
@@ -244,7 +245,7 @@ export const InnerFlatList = React.forwardRef<
contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}>
<FlatList
ref={ref}
style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, flatten(style)]}
style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, style]}
{...props}
/>
{footer}
+1 -1
View File
@@ -186,7 +186,7 @@ export function Description({
return rt
}, [description])
if (!rt) return null
return <RichText value={rt} style={[a.leading_snug]} disableLinks {...rest} />
return <RichText value={rt} disableLinks {...rest} />
}
export function DescriptionPlaceholder() {
+2 -11
View File
@@ -2,7 +2,6 @@ import {View} from 'react-native'
import {
atoms as a,
flatten,
type TextStyleProp,
useTheme,
type ViewStyleProp,
@@ -33,17 +32,9 @@ export function IconCircle({
height: size === 'lg' ? 52 : 64,
backgroundColor: t.palette.primary_50,
},
flatten(style),
style,
]}>
<Icon
size={size}
style={[
{
color: t.palette.primary_500,
},
flatten(iconStyle),
]}
/>
<Icon size={size} style={[{color: t.palette.primary_500}, iconStyle]} />
</View>
)
}
+7 -2
View File
@@ -83,7 +83,12 @@ export function InterestTabs({
function handleTabLayout(index: number, x: number, width: number) {
if (!tabOffsets.length) {
pendingTabOffsets.current[index] = {x, width}
if (pendingTabOffsets.current.length === interests.length) {
// not only do we check if the length is equal to the number of interests,
// but we also need to ensure that the array isn't sparse. `.filter()`
// removes any empty slots from the array
if (
pendingTabOffsets.current.filter(o => !!o).length === interests.length
) {
setTabOffsets(pendingTabOffsets.current)
}
}
@@ -205,7 +210,7 @@ export function InterestTabs({
showsHorizontalScrollIndicator={false}
decelerationRate="fast"
snapToOffsets={
tabOffsets.length === interests.length
tabOffsets.filter(o => !!o).length === interests.length
? tabOffsets.map(o => o.x - tokens.space.xl)
: undefined
}
+1 -1
View File
@@ -55,7 +55,7 @@ export function Description({value, handle}: {value?: string; handle: string}) {
const {_} = useLingui()
return value ? (
<Text numberOfLines={2}>
<RichText value={value} style={[a.leading_snug]} />
<RichText value={value} />
</Text>
) : (
<Text emoji style={[a.leading_snug]}>
+4 -2
View File
@@ -129,7 +129,6 @@ export function BackButton({onPress, style, ...props}: Partial<ButtonProps>) {
size="small"
variant="ghost"
color="secondary"
shape="square"
onPress={onPressBack}
hitSlop={HITSLOP_30}
style={[
@@ -164,7 +163,10 @@ export function MenuButton() {
shape="square"
onPress={onPress}
hitSlop={HITSLOP_30}
style={[{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET}]}>
style={[
{marginLeft: -BUTTON_VISUAL_ALIGNMENT_OFFSET},
a.bg_transparent,
]}>
<ButtonIcon icon={Menu} size="lg" />
</Button>
</Slot>
+2 -2
View File
@@ -166,7 +166,7 @@ export function useLink({
const state = navigation.getState()
// if screen is not in the current navigator, it means it's
// most likely a tab screen. note: state can be undefined
if (!state?.routeNames.includes(screen)) {
if (!state?.routeNames?.includes?.(screen)) {
const parent = navigation.getParent()
if (
parent &&
@@ -296,7 +296,7 @@ export function Link({
return (
<Button
{...rest}
style={[a.justify_start, flatten(rest.style)]}
style={[a.justify_start, rest.style]}
role="link"
accessibilityRole="link"
href={href}
+29 -2
View File
@@ -4,8 +4,12 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {cleanError} from '#/lib/strings/errors'
import {
EmptyState,
type EmptyStateButtonProps,
} from '#/view/com/util/EmptyState'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Error} from '#/components/Error'
import {Loader} from '#/components/Loader'
@@ -43,7 +47,7 @@ export function ListFooter({
a.pb_lg,
t.atoms.border_contrast_low,
{height: height ?? 180, paddingTop: 30},
flatten(style),
style,
]}>
{isFetchingNextPage ? (
<Loader size="xl" />
@@ -129,6 +133,9 @@ let ListMaybePlaceholder = ({
hideBackButton,
sideBorders,
topBorder = false,
emptyStateIcon,
emptyStateButton,
useEmptyState = false,
}: {
isLoading: boolean
noEmpty?: boolean
@@ -143,6 +150,9 @@ let ListMaybePlaceholder = ({
hideBackButton?: boolean
sideBorders?: boolean
topBorder?: boolean
emptyStateIcon?: React.ComponentType<any> | React.ReactElement
emptyStateButton?: EmptyStateButtonProps
useEmptyState?: boolean
}): React.ReactNode => {
const t = useTheme()
const {_} = useLingui()
@@ -180,6 +190,23 @@ let ListMaybePlaceholder = ({
)
}
if (useEmptyState) {
return (
<View style={[t.atoms.border_contrast_low]}>
<EmptyState
icon={emptyStateIcon}
message={
emptyMessage ??
(emptyType === 'results'
? _(msg`No results found`)
: _(msg`Page not found`))
}
button={emptyStateButton}
/>
</View>
)
}
if (!noEmpty) {
return (
<Error
+2 -7
View File
@@ -7,7 +7,7 @@ import Animated, {
withTiming,
} from 'react-native-reanimated'
import {atoms as a, flatten, useTheme} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import {type Props, useCommonSVGProps} from '#/components/icons/common'
import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader'
@@ -37,12 +37,7 @@ export function Loader(props: Props) {
]}>
<Icon
{...props}
style={[
a.absolute,
a.inset_0,
t.atoms.text_contrast_high,
flatten(props.style),
]}
style={[a.absolute, a.inset_0, t.atoms.text_contrast_high, props.style]}
/>
</Animated.View>
)
+2 -2
View File
@@ -1,6 +1,6 @@
import {View} from 'react-native'
import {atoms as a, flatten, useTheme} from '#/alf'
import {atoms as a, useTheme} from '#/alf'
import {type Props, useCommonSVGProps} from '#/components/icons/common'
import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader'
@@ -24,7 +24,7 @@ export function Loader(props: Props) {
a.absolute,
a.inset_0,
t.atoms.text_contrast_high,
flatten(props.style),
props.style,
]}
/>
</div>
+13 -5
View File
@@ -1,6 +1,8 @@
import {StyleSheet} from 'react-native'
import type React from 'react'
import {atoms as a, useTheme, type ViewStyleProp} from '#/alf'
import {isHighDPI} from '#/lib/browser'
import {atoms as a, platform, useTheme, type ViewStyleProp} from '#/alf'
import {Fill} from '#/components/Fill'
/**
@@ -25,7 +27,15 @@ export function MediaInsetBorder({
<Fill
style={[
a.rounded_md,
a.border,
{
borderWidth: platform({
native: StyleSheet.hairlineWidth,
// while we generally use hairlineWidth (aka 1px),
// we make an exception here for high DPI screens
// as the 1px border is very noticeable -sfn
web: isHighDPI ? 0.5 : StyleSheet.hairlineWidth,
}),
},
opaque
? [t.atoms.border_contrast_low]
: [
@@ -34,9 +44,7 @@ export function MediaInsetBorder({
: t.atoms.border_contrast_high,
{opacity: 0.6},
],
{
pointerEvents: 'none',
},
a.pointer_events_none,
style,
]}>
{children}
+1 -1
View File
@@ -170,7 +170,7 @@ export function FollowsYou({size = 'sm'}: CommonProps) {
}, [size])
return (
<View style={[variantStyles, a.justify_center, t.atoms.bg_contrast_25]}>
<View style={[variantStyles, a.justify_center, t.atoms.bg_contrast_50]}>
<Text style={[a.text_xs, a.leading_tight]}>
<Trans>Follows You</Trans>
</Text>
@@ -5,11 +5,11 @@ import {
useSafeAreaInsets,
} from 'react-native-safe-area-context'
import {LinearGradient} from 'expo-linear-gradient'
import {utils} from '@bsky.app/alf'
import {isAndroid, isNative} from '#/platform/detection'
import {useA11y} from '#/state/a11y'
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
import {transparentifyColor} from '#/alf/util/colorGeneration'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {FocusScope} from '#/components/FocusScope'
import {LockScroll} from '#/components/LockScroll'
@@ -47,7 +47,7 @@ export function Overlay({
) : (
<LinearGradient
colors={[
transparentifyColor(t.atoms.bg.backgroundColor, 0),
utils.alpha(t.atoms.bg.backgroundColor, 0),
t.atoms.bg.backgroundColor,
t.atoms.bg.backgroundColor,
]}
@@ -97,7 +97,7 @@ export function Overlay({
]}>
<LinearGradient
colors={[
transparentifyColor(t.atoms.bg.backgroundColor, 0),
utils.alpha(t.atoms.bg.backgroundColor, 0),
t.atoms.bg.backgroundColor,
]}
start={[0.5, 0]}
@@ -113,7 +113,7 @@ export function Overlay({
role="dialog"
aria-role="dialog"
aria-label={label}
style={flatten([
style={[
a.relative,
a.w_full,
a.p_2xl,
@@ -128,7 +128,7 @@ export function Overlay({
maxWidth: 420,
}),
],
])}>
]}>
{children}
</View>
</FocusScope>
@@ -5,3 +5,10 @@ import {ID} from '#/components/PolicyUpdateOverlay/updates/202508/config'
* the relationship is clear.
*/
export const ACTIVE_UPDATE_ID = ID
/**
* Toggle to enable or disable the policy update overlay feature e.g. once an
* update has run its course, set this to false. For new updates, set this to
* true and change `ACTIVE_UPDATE_ID` to the new update ID.
*/
export const POLICY_UPDATE_IS_ENABLED = false
+10 -2
View File
@@ -7,6 +7,7 @@ import {
} from 'react'
import {useSession} from '#/state/session'
import {POLICY_UPDATE_IS_ENABLED} from '#/components/PolicyUpdateOverlay/config'
import {Provider as PortalProvider} from '#/components/PolicyUpdateOverlay/Portal'
import {
type PolicyUpdateState,
@@ -45,8 +46,15 @@ export function Provider({children}: {children?: ReactNode}) {
const {hasSession} = useSession()
const [isReadyToShowOverlay, setIsReadyToShowOverlay] = useState(false)
const state = usePolicyUpdateState({
// only enable the policy update overlay in non-test environments
enabled: isReadyToShowOverlay && hasSession && ENV !== 'e2e',
enabled:
// if the feature is enabled
POLICY_UPDATE_IS_ENABLED &&
// once shell has rendered
isReadyToShowOverlay &&
// only once logged in
hasSession &&
// only enabled in non-test environments
ENV !== 'e2e',
})
const ctx = useMemo(
+4 -1
View File
@@ -64,7 +64,10 @@ export function ImageEmbed({
}
const onPressIn = (_: number) => {
InteractionManager.runAfterInteractions(() => {
Image.prefetch(items.map(i => i.uri))
Image.prefetch(
items.map(i => i.uri),
'memory',
)
})
}
@@ -7,7 +7,6 @@ import type * as HlsTypes from 'hls.js'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {atoms as a} from '#/alf'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import * as BandwidthEstimate from './bandwidth-estimate'
import {Controls} from './web-controls/VideoControls'
@@ -102,7 +101,6 @@ export function VideoEmbedInnerWeb({
hasSubtitleTrack={hasSubtitleTrack}
/>
</div>
<MediaInsetBorder />
</View>
)
}
+9 -29
View File
@@ -7,7 +7,7 @@ import {useLingui} from '@lingui/react'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {Button} from '#/components/Button'
import {useThrottledValue} from '#/components/hooks/useThrottledValue'
import {PlayButtonIcon} from '#/components/video/PlayButtonIcon'
@@ -16,11 +16,9 @@ import * as VideoFallback from './VideoEmbedInner/VideoFallback'
interface Props {
embed: AppBskyEmbedVideo.View
crop?: 'none' | 'square' | 'constrained'
}
export function VideoEmbed({embed, crop}: Props) {
const t = useTheme()
export function VideoEmbed({embed}: Props) {
const [key, setKey] = useState(0)
const renderError = useCallback(
@@ -40,13 +38,10 @@ export function VideoEmbed({embed, crop}: Props) {
}
let constrained: number | undefined
let max: number | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
}
const cropDisabled = crop === 'none'
const contents = (
<ErrorBoundary renderError={renderError} key={key}>
@@ -56,28 +51,13 @@ export function VideoEmbed({embed, crop}: Props) {
return (
<View style={[a.pt_xs]}>
{cropDisabled ? (
<View
style={[
a.w_full,
a.overflow_hidden,
{aspectRatio: max ?? 1},
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{contents}
</View>
) : (
<ConstrainedImage
fullBleed={crop === 'square'}
aspectRatio={constrained || 1}
// slightly smaller max height than images
// images use 16 / 9, for reference
minMobileAspectRatio={14 / 9}>
{contents}
</ConstrainedImage>
)}
<ConstrainedImage
aspectRatio={constrained || 1}
// slightly smaller max height than images
// images use 16 / 9, for reference
minMobileAspectRatio={14 / 9}>
{contents}
</ConstrainedImage>
</View>
)
}
@@ -17,6 +17,7 @@ import {ConstrainedImage} from '#/view/com/util/images/AutoSizedImage'
import {atoms as a, useTheme} from '#/alf'
import {useIsWithinMessage} from '#/components/dms/MessageContext'
import {useFullscreen} from '#/components/hooks/useFullscreen'
import {MediaInsetBorder} from '#/components/MediaInsetBorder'
import {
HLSUnsupportedError,
VideoEmbedInnerWeb,
@@ -25,13 +26,7 @@ import {
import {useActiveVideoWeb} from './ActiveVideoWebContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
export function VideoEmbed({
embed,
crop,
}: {
embed: AppBskyEmbedVideo.View
crop?: 'none' | 'square' | 'constrained'
}) {
export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
const t = useTheme()
const ref = useRef<HTMLDivElement>(null)
const {active, setActive, sendPosition, currentActiveView} =
@@ -76,13 +71,10 @@ export function VideoEmbed({
}
let constrained: number | undefined
let max: number | undefined
if (aspectRatio !== undefined) {
const ratio = 1 / 2 // max of 1:2 ratio in feeds
constrained = Math.max(aspectRatio, ratio)
max = Math.max(aspectRatio, 0.25) // max of 1:4 in thread
}
const cropDisabled = crop === 'none'
const contents = (
<div
@@ -91,8 +83,11 @@ export function VideoEmbed({
display: 'flex',
flex: 1,
cursor: 'default',
backgroundColor: t.palette.black,
backgroundImage: `url(${embed.thumbnail})`,
backgroundSize: 'cover',
backgroundSize: 'contain',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
onClick={evt => evt.stopPropagation()}>
<ErrorBoundary renderError={renderError} key={key}>
@@ -114,28 +109,15 @@ export function VideoEmbed({
<ViewportObserver
sendPosition={sendPosition}
isAnyViewActive={currentActiveView !== null}>
{cropDisabled ? (
<View
style={[
a.w_full,
a.overflow_hidden,
{aspectRatio: max ?? 1},
a.rounded_md,
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
{contents}
</View>
) : (
<ConstrainedImage
fullBleed={crop === 'square'}
aspectRatio={constrained || 1}
// slightly smaller max height than images
// images use 16 / 9, for reference
minMobileAspectRatio={14 / 9}>
{contents}
</ConstrainedImage>
)}
<ConstrainedImage
fullBleed
aspectRatio={constrained || 1}
// slightly smaller max height than images
// images use 16 / 9, for reference
minMobileAspectRatio={14 / 9}>
{contents}
<MediaInsetBorder />
</ConstrainedImage>
</ViewportObserver>
</View>
)

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