Compare commits

..

167 Commits

Author SHA1 Message Date
Eric Bailey ebbe3ac888 Very load more copy 2024-06-14 12:26:25 -05:00
Eric Bailey 0e02c54134 Expand avis 2024-06-14 12:06:28 -05:00
Eric Bailey 76c1b6f726 Border 2024-06-14 12:02:45 -05:00
Eric Bailey 9937f8b7be Update query keys 2024-06-14 12:01:13 -05:00
Eric Bailey cf44a96da6 Make sure it's hairline 2024-06-14 11:57:36 -05:00
Eric Bailey 25aa7c56e7 The thin blue line 2024-06-14 11:57:36 -05:00
Eric Bailey 6bad2e9d4f Header styles 2024-06-14 11:57:36 -05:00
Eric Bailey 8565a5222e Load more styling 2024-06-14 11:57:36 -05:00
Eric Bailey ef241e684b Update copy 2024-06-14 11:57:36 -05:00
Eric Bailey c21501e94c Revert unneeded change v2 2024-06-14 11:57:36 -05:00
Eric Bailey 6556ce28af Revert unneeded change 2024-06-14 11:57:36 -05:00
Eric Bailey 0d2b9797b1 Add tab, query, factor out 2024-06-14 11:57:36 -05:00
Eric Bailey a1f03f1571 Abstract 2024-06-14 11:57:36 -05:00
Eric Bailey 84df33d1fc Some styling tweaks 2024-06-14 11:57:36 -05:00
Eric Bailey e0372afc5d Lower limit 2024-06-14 11:57:36 -05:00
Eric Bailey 2bf1633cf8 Load more styling 2024-06-14 11:57:36 -05:00
Eric Bailey 367d301ccd Remove circle icons 2024-06-14 11:57:36 -05:00
Eric Bailey 9fb8f9cac0 Redo explore page, wip 2024-06-14 11:57:36 -05:00
Eric Bailey fe3f872d49 Add known followers to shadow cache (#4517) 2024-06-14 11:56:43 -05:00
Eric Bailey 4c0f037880 Reuse overfetching for popular feeds, add in existing filtering (#4501) 2024-06-14 11:56:29 -05:00
Samuel Newman bdeac28d74 Try/catch URL parsing of referrer (#4511) 2024-06-13 15:06:22 -07:00
Hailey d9066a6beb add document.referrer to statsig custom (#4509)
* add referrer to statsig custom

dont include referrer if hostname is bsky.app

save

add `document.referrer` to statsig custom

* add a hostname field

* account for ssr

* account for ssr
2024-06-13 23:31:19 +02:00
Samuel Newman d20c59e19d Calculate correct keyboard offset in composer (#4500)
* calculate correct keyboard offset

* give viewHeight a default value

* much simpler approach
2024-06-13 15:58:56 +01:00
dan 498e46ae4e Hide bio and social proof for blocked users (#4504) 2024-06-13 11:03:31 +02:00
Minseo Lee 7faa1d9131 KnownFollowers localization (#4494)
* Update KnownFollowers.tsx

* Update KnownFollowers.tsx

* Update KnownFollowers.tsx
2024-06-13 04:58:25 +02:00
Eric Bailey 28ba99917a Fix profile hover card blocked state (#4480)
* Fix: mini profile on hover allows following a blocker/blocked user (#4423) (#4440)

* Tweaks

---------

Co-authored-by: Michał Gołda <michal.golda@hotmail.com>
2024-06-13 04:53:32 +02:00
Eric Bailey d989128e5b Set profile hover prefetch stale time to 30s (#4417)
* Set prefetch stale time to 30s

* Run prefetch on mouseOver

* Only prefetch once on mousemove
2024-06-13 04:44:06 +02:00
Mary 247af5aee9 Prevent rich-formatting paste (#4327)
* fix: prevent rich-formatting paste

* fix: return true instead of preventDefault
2024-06-13 04:37:49 +02:00
dan ebd4f93b9c Show social proof in hovercards (#4502)
* Add social proof to hovercards

* Close it more reliably
2024-06-13 04:23:37 +02:00
Eric Bailey a55f924639 Just use server count (#4499)
* Just use server count

* Fix count
2024-06-12 16:52:00 -05:00
Hailey 99078bbff2 Don't show warning when sharing your own post in PWI opt-out mode (#4495) 2024-06-12 08:51:08 -07:00
Kuwa Lee 4e4a7bf18f Update Chinese localization (#4410)
* TW: Update and clean

* CN:run intl:extract

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* TW: unify "登入"

* Update messages.po

* Update messages.po

* BOTH: fix "Post"

msgctxt "description"
msgid "Post"

* CN: Update translates

* CN: Remove superseded strings

* TW: Update and clean

* CN: fix msgid "This feed is empty."

---------

Co-authored-by: Frudrax Cheng <i@cynosura.one>
Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>
2024-06-11 17:17:47 -07:00
Paul Frazee 6736384ff4 Release 1.86 prep (#4490)
* Test updates

* Run intl extract
2024-06-11 16:20:26 -07:00
Eric Bailey bb0a6a4b6c Add KnownFollowers component to standard profile header (#4420)
* Add KnownFollowers component to standard profile header

* Prep for known followers screen

* Add known followers screen

* Tighten space

* Add pressed state

* Edit title

* Vertically center

* Don't show if no known followers

* Bump sdk

* Use actual followers.length to show

* Updates to show logic, space

* Prevent fresh data from applying to cached screens

* Tighten space

* Better label

* Oxford comma

* Fix count logic

* Add bskyweb route

* Useless ternary

* Minor spacing tweak

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
2024-06-11 15:42:28 -07:00
Marco Maroni 7011ac8f72 Update italian localization (#4374)
* Update messages.po

Update italian localization

* Fix translation

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

Fix by @surfdude29

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

* Added some translation

---------

Co-authored-by: Marco Maroni <marcomaroni@gmail.com>
Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
2024-06-11 15:29:38 -07:00
Minseo Lee 815ab79309 Update Korean localization (#4387)
* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po
2024-06-11 15:28:36 -07:00
surfdude29 43f6b68aa2 Update French localization (#4437)
* Update French localization

* use `point médian`

* Update messages.po

* add three new strings

* translate new string

* Apply suggestions from code review

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

* update revision date

---------

Co-authored-by: Stanislas Signoud <signez@stanisoft.net>
2024-06-11 15:27:34 -07:00
Kevin Scannell 0640364e0f Irish back to 100% (#4454) 2024-06-11 15:26:08 -07:00
Paul Frazee e1dcd2e434 Fix to thread load-more bug (#4488) 2024-06-11 15:01:13 -07:00
Eric Bailey a4ca4db35c Stringify path error (#4379) 2024-06-11 14:54:47 -07:00
Samuel Newman 3d4b390a8a Only enable keyboard controller when necessary (#4483)
* Only enable keyboard controller when necessary

* make it screen only

* rm keyboard padding

* rm keyboardpadding file

* revert using keyboard controller in composer

* remove styles.outer (unnecessary for revert)

* continue to use keyboard padding in the report dialog for dms

---------

Co-authored-by: Hailey <me@haileyok.com>
2024-06-11 12:50:56 -07:00
Paul Frazee aca0fa23ec Update feeds header to just have edit link (#4484) 2024-06-11 12:34:03 -07:00
Paul Frazee 46e12c6d34 Improve thread loading (#4402)
* Increase the number of posts loaded when a self-thread is present

* Increase depth to 10, detect cutoffs on self-threads and show continue link

* Stacky the avis
2024-06-11 11:30:38 -07:00
Eric Bailey 4b6609d48b Move feeds screen into common navigator, handle usages (#4365)
* Move feeds screen into common navigator, handle usages

* Add link to Feeds from home screen (#4366)

* Add link to feeds to home screen header

* Center logo

* Replace icons

* Tweak spacing

* Tweak spacing

* Swap icon, sizing

* Buttonize, size

* Make menu same alignment on all screens

* Remove FeedsTab support, enable drawer swipe on MessagesTab

* Add note

* Vertically align header

* Swap in Pin

* Use hashtag icon

* Remove png

* Fix reference

* Ensure alignment with home and other screens
2024-06-11 11:08:06 -07:00
Samuel Newman 3573f7ea40 rip out native translate (#4482) 2024-06-11 10:51:03 -07:00
Hailey fac5f6cdac register the push token after request has been approved on android (#4481)
* register the push token after request has been approved on android

* use a `0` string for badge count

* Revert "use a `0` string for badge count"

This reverts commit efac398618.

* temporary fix
2024-06-11 10:28:48 -07:00
Hailey 808dd3569d patch expo-notifications to clear badge (#4475) 2024-06-10 20:19:19 -07:00
Samuel Newman d85c8a0976 Revert to old modal on android (#4458)
* revert to old modal on android

* close alf dialogs before closing composer

* Try to fix white area

* Use hook

* Fix Back button

* oops

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-11 05:10:57 +02:00
Eric Bailey 14cddb7ec0 Sort profile feedgens by likes (#4380)
* Sort profile feedgens by likes

* Sort entire set as we get more pages

* Nah

This reverts commit 3b8714fd97.

* Update profile-feedgens.ts

---------

Co-authored-by: dan <dan.abramov@gmail.com>
2024-06-11 03:55:44 +02:00
Eric Bailey 848151c4f9 Revert "Fix undefined block (#4471)" (#4472)
This reverts commit 7cac413f3b.
2024-06-11 03:49:28 +02:00
Eric Bailey 7cac413f3b Fix undefined block (#4471)
* Fix undefined block (#4378)

* Fix undefined block

* Changing text and handling all blocks.

* Tweaks

* Hide follow button in hover card if user blocked

---------

Co-authored-by: Josh <hi@oracularhades.com>
2024-06-10 20:45:23 -05:00
Eric Bailey 212f5750e3 Open convo menu when tapping on chat with deleted account (#4470)
* Open menu on click

* Remove useless util

* Improve label

* s/press/click

* Tweak message

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-11 03:36:42 +02:00
Hailey c73ad43a80 stop using addPushTokenListener (#4467)
* stop using push token listener

* cleanup the hack

* add a comment

* remove test code

* Fix patch

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-11 03:16:21 +02:00
Samuel Newman 4e9a65200e add hitslop to repost button (#4469) 2024-06-10 16:43:04 -07:00
Paul Frazee 86e81650d3 Add a loading indicator to notifications when loading latest (#4468)
* Add a loading indicator to notifications when loading latest

* Adjust size and alignment
2024-06-11 01:17:34 +02:00
Paul Frazee 4efd576f6a Visually improve the empty state of feeds (#4466) 2024-06-10 13:44:21 -07:00
Paul Frazee c2d7d23423 Give a meaningful error in the composer when replying to a deleted post (#4464) 2024-06-10 22:02:00 +02:00
Paul Frazee 0404111f03 Fix: show known feed error UI more reliably (#4463) 2024-06-10 22:01:14 +02:00
Eric Bailey 7356763e49 Only validate persisted data for existing users (#4465) 2024-06-10 22:00:45 +02:00
Hailey 5dd195bcb7 Check canAskAgain for notification permissions (#4460) 2024-06-10 12:14:00 -07:00
Paul Frazee 90ec22a674 Add support for new-tab clicks on feeds (#4462) 2024-06-10 11:44:13 -07:00
Paul Frazee 59f49bef68 Override legacy language codes for indonesian, hebrew, and yiddish (#4461)
* Manually override incorrect 'in' to 'id' lang code

* Fix additional legacy language code issues
2024-06-10 20:37:14 +02:00
dan 620ab88713 Ungate mobile web notification badge (#4459) 2024-06-10 18:17:26 +02:00
Francesco Lodovici b688da8d58 Fix (#4430): Use separate hooks for shell mode animated styles (#4451)
* Fix (#4430): Use separate hooks for shell mode animated styles

* Consolidate in one file

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-10 17:19:28 +02:00
Samuel Newman fd03ea3fe1 Throttle instead of debounce (#4456)
* throttle instead of debounce

* trailing: true

* Fix throttle call

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-10 17:02:57 +02:00
Samuel Newman 1317d881ed debounce refetching listconvos (#4455) 2024-06-10 16:08:13 +02:00
dan 01b7a94a7e Patch to work around a crash in RN internals (#4426)
Co-authored-by: Hailey <me@haileyok.com>
2024-06-08 01:55:57 -07:00
dan a2d1cf68b9 Reliably focus keyboard on Android (#4427) 2024-06-07 22:27:16 -07:00
Hailey 5f63b8d40f Bump expo-notifications to fix NPE (#4425)
* bump library

* rename patch file
2024-06-08 03:17:00 +01:00
Hailey 480a40862f Use the proper logic on iOS to increment the badge (#4233) 2024-06-07 15:15:33 -07:00
Hailey c58aedf050 Bump 1.86.0 (#4411) 2024-06-07 20:00:50 +01:00
Samuel Newman 29a4a5f90c Composer - backup android focus (#4415)
* backup android focus

* bump to 300ms just to make sure it catches all of them
2024-06-07 13:32:38 +01:00
Hailey fefae27396 scope enable/disable of keyboard controller in composer to only android (#4412) 2024-06-06 22:29:34 -07:00
Minseo Lee 2540616f43 Fix horizontal padding of tab within profile header (#3924)
Co-authored-by: Hailey <me@haileyok.com>
2024-06-06 21:41:03 -07:00
Paul Frazee bd1e6fced9 1.85.1 2024-06-06 12:18:08 -07:00
Hailey 885ad2c756 disable keyboard controller in the composer screen (#4399)
* disable keyboard controller in the composer screen

* add comment

* oops

* destructure

* rename to keyboard context for clarity

* move destructure

* cleanup
2024-06-06 12:15:36 -07:00
Paul Frazee fef16e060f Fix notifications pagination on web (#4400)
* Fix notifications pagination on web

* Preserve larger threshold on native
2024-06-06 12:10:50 -07:00
dan ac25e43bef Patch Expo Modules for Android crash (#4398)
* Patch Expo Modules for Android crash

* rm intermediaries, add a readme

---------

Co-authored-by: Hailey <me@haileyok.com>
2024-06-06 11:21:38 -07:00
Hailey 094c08ea60 Revert "Change Android's MainActivity launchMode to singleTop" (#4397)
* Revert "Change Android's MainActivity launchMode to singleTop (#4255)"

This reverts commit 65ad16e394.

* Update bundle-deploy-eas-update.yml

---------

Co-authored-by: dan <dan.abramov@gmail.com>
2024-06-06 19:08:20 +01:00
dan 559764156d Fix tall image lightbox on Android (#4393) 2024-06-06 15:13:53 +01:00
dan 3801932902 Fix prettier (#4392) 2024-06-06 15:00:05 +01:00
Hailey 72f46ed734 cancel animations before updating value for min-shell animation (#4386)
* cancel animations before updating value

* comment
2024-06-06 14:28:02 +01:00
Samuel Newman 85e676257e Composer - make sure android keyboard opens (#4390)
* keep trying to open keyboard until it's open

* limit number of retries

* keep the original 50ms one as well

* Proper fix!

* disable autoFocus if not visible

* Reset derived state

* Revert "Reset derived state"

This reverts commit 71f57391ae.

* Use derived state pattern

* Rename for clarity

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-06 14:21:22 +01:00
dan 48796449ea Switch to withSpring to work around Reanimated regression (#4391) 2024-06-06 14:17:26 +01:00
dan 1f954c1065 Split ShareViaChatDialog (#4383) 2024-06-06 01:10:21 +01:00
dan 5d2119ffe5 Pin resolution of Statsig Expo dependencies (#4382)
* Force resolution of expo-constants

* Pin expo-device
2024-06-06 00:40:28 +01:00
Hailey 492c271a06 add legacy to picker for Android (#4377)
* add `legacy` to picker

* add for the other callsite
2024-06-05 16:27:57 -07:00
Hailey 42477d8bbb add notification count badge mobile web (#4375)
* add notification count badge mobile web

* rename var

* add gate to types

* implement gate

* nit

* Fix condition order, narrow types

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-05 18:13:42 +01:00
Hailey aefbab3850 Remove expo-constants (#4371) 2024-06-05 09:05:25 -07:00
Hailey af2aaa1f39 add types for desktopFixedHeight to List (#4356)
* add types for `desktopFixedHeight`

* nit

* accept both `number` and `boolean`
2024-06-05 09:03:40 -07:00
Paul Frazee 22858192dc Release 1.85 (#4372)
* Update tests

* Run intl extract
2024-06-04 20:00:21 -07:00
Minseo Lee 618a3c1ebe Update Korean localization (#4318)
* Update messages.po

* Update messages.po

* Update messages.po

* Update messages.po

* Merge remote-tracking branch 'upstream/main' into update-korean-localization
2024-06-04 19:55:05 -07:00
Kevin Scannell 5e0452fc6b Update Irish translations back to 100% (#4306) 2024-06-04 19:54:38 -07:00
Stanislas Signoud 47033e5270 Update French localizations (#4223)
* Update French localizations

* Fix typos in French and misusage of "Ignorer"

Thanks @surfdude29 for the review!

Fixes #4296.

* Bump strings and remove unused one in French

* Translate three new missing strings in French

* Bump strings and trim unused old in French again

* Translate five new missing strings in French again
2024-06-04 19:53:59 -07:00
Takayuki KUSANO bb9afa9ce8 Upated Japanese translation (#4311)
* Delete obsoleted translated messages

* Translate new messages, update some messages

* Update Japanese translations
2024-06-04 19:52:58 -07:00
Frudrax Cheng 504bd28e80 Update Chinese Localization (#4323)
* Improve zh-TW translation

* improve and fix

* Update messages.po

msgid "Nevermind, create a handle for me"

* revert L3813

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* Update src/locale/locales/zh-TW/messages.po

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>

* BOTH: Fix and clean

msgid "Copy {0}"

msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information."

* CN: Unify emoji

* TW: Update and unify some translation

* TW: unify "色情

* TW: update

* TW: typo

* CN:translated latest commit

* CN:removed superseded strings

* CN:fixed string 'left to go'

* CN:unified translation

* CN:translated latest commit

* CN:fix typo

---------

Co-authored-by: cirx <133132480+cirx1e@users.noreply.github.com>
Co-authored-by: Kuwa Lee <kuwalee1069@gmail.com>
2024-06-04 19:52:08 -07:00
Hailey afbcac3ff3 use a timeout to focus the composer input (#4370)
* use a timeout to focus the composer input

* scope to just android

* scope useEffect to just android as well

* oops

* cleanup
2024-06-05 03:44:43 +01:00
Eric Bailey deea2f3839 Clarify some things in OTA docs (#4367)
* Clarify some things

* add github outputs script

* add slack notify android

* test slack android

* fix indent

* fix test

* sigh...

* Revert "fix test"

This reverts commit c99764464f.

* Revert "fix indent"

This reverts commit 4cce508d28.

* Revert "test slack android"

This reverts commit b02419b247.

* test ios workflow

* remove testing

* add slack info to docs

* use correct output for android

---------

Co-authored-by: Hailey <me@haileyok.com>
2024-06-04 18:55:18 -07:00
Paul Frazee 7613cdb89b Fix: visually indicate when quoted content is labeled (#4369) 2024-06-05 02:19:26 +01:00
Eric Bailey 3ece21cb45 [🙅] Integrate deactivate (#4308)
* Update types

(cherry picked from commit 27deac1f367825771ba76fa098ec1b0a62dcf64a)

* Integrate into deactivate dialog

(cherry picked from commit 84f299a447259cc1fbfc7be607e28197779e4ec1)

* Integrate into Deactivated screen

(cherry picked from commit 29193f34822ecdf11e2a407197fa230285dfe846)

* Bump api sdk

(cherry picked from commit 738c622d3e5a23bfbb0d3bdce3a6bdf01e54ca60)

* Update permalink

(cherry picked from commit c10bf5c071d76c3054bc4ce9d313c10b1820f038)

* Bump sdk pkg

* Update types to match backend

* Loosen types for forwards compat

* Hydrate status from persisted data

* Refresh session when re-activating, clear query cache

* Show app password error

* Refactor dialog to clear state when closed

* Add app password error to Deactivated screen
2024-06-05 02:02:22 +01:00
Eric Bailey e64b7cf698 Fix descenders cutoff in new chat dialog (#4359) 2024-06-04 20:51:38 +01:00
Eric Bailey a2d5343a87 Report persisted schema validation failures (#4358)
* Report persisted schema validation failures

* Make it safer
2024-06-04 20:51:28 +01:00
dan b5ac450442 Don't show profile labels until loaded (#4357) 2024-06-04 19:54:30 +01:00
Eric Bailey 551af88f22 [🙅] Remove fallback that's no longer valid (#4353)
* Remove fallback that's no longer valid

* Update test
2024-06-04 19:36:07 +01:00
Eric Bailey a49fe13223 Use recent convos for share via dialog (#4352) 2024-06-04 19:35:07 +01:00
Hailey 9f001526d3 Fix a few border nits (#4349)
* replace w/ hairline width

* no border for placeholder

* few notifications screen fixes tablet

* still show the border on desktop

* Simp

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-04 19:31:54 +01:00
surfdude29 d6b8313932 Mark accessibilityLabel and accessibilityHint for translation (#4351)
* mark `accessibilityLabel` and `accessibilityHint` for translation

* lint

* try again
2024-06-04 19:15:28 +01:00
Hailey e4b4d854d6 use rngh scrollview in search horizontal list (#4350) 2024-06-04 11:06:31 -07:00
dan c352e0f8b4 Fix forwarded ref (#4348) 2024-06-04 18:36:00 +01:00
Ryan Skinner e7968bc8d7 add profiles to search history (#4169)
* add profiles to search history

* increasing horizontal padding slightly

* tightening up styling

* fixing navigation issue

* making corrections

* Make the search history profiles a little smaller

* bug stomping

* Fix issues

* Persist taps

* Rm unnecessary

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-04 16:31:24 +01:00
dan 6f1589971c Fix missing top borders (#4346) 2024-06-04 11:07:11 +01:00
Samuel Newman 2ffb98e22a allow nested quotes in DMs (#4345) 2024-06-04 11:03:43 +01:00
Samuel Newman d918f8dc2a Composer - unbork web (#4344)
* reduce side gap + add overflow hidden

also remove the animations since they don't appear in prod, and are kinda broken

* removed fixed height to fix alt text
2024-06-04 10:58:09 +01:00
Samuel Newman d032734278 Composer - make bottom border more consistent when typing (#4343)
* floor values

* fix last line being obscured

* Rm unnecessary runOnUI

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-06-04 10:55:35 +01:00
Eric Bailey 3e1f076891 [🙅] Disambiguation of the deactivation (#4267)
* Disambiguation of the deactivation

* Snapshot crackle pop

* Change log context

* [🙅] Add status to session state (#4269)

* Add status to session state

* [🙅] Add new deactivated screen (#4270)

* Add new deactivated screen

* Update copy, handle logout

* Remove icons, adjust padding

* [🙅] Add deactivate account dialog (#4290)

* Deactivate dialog

(cherry picked from commit 33940e2dfe0d710c0665a7f68b198b46f54db4a2)

* Factor out dialog, add to delete modal too

(cherry picked from commit 47d70f6b74e7d2ea7330fd172499fe91ba41062d)

* Update copy, icon

(cherry picked from commit e6efabbe78c3f3d9f0f8fb0a06a6a1c4fbfb70a9)

* Update copy

(cherry picked from commit abb0ce26f6747ab0548f6f12df0dee3c64464852)

* Sizing tweaks

(cherry picked from commit fc716d5716873f0fddef56496fc48af0614b2e55)

* Add a11y label
2024-06-04 02:10:43 +01:00
Eric Bailey de93e8de74 [🐴] Post embeds polish (#4339)
* Handle message cleanup

* Handle last message in chat list

* Memoize lastMessage
2024-06-04 02:07:01 +01:00
Samuel Newman da96fb1ef5 Native formSheet for GIF select on iOS (#4328)
* native formsheet for gif select

* trigger confirm discard if have gif

* give modal a background color

* fix web top bar - unrelated but I cba to make a separate PR
2024-06-04 02:05:46 +01:00
Hailey b02445883a add an apk to production build outputs for Obtanium release support (#4317)
* add an apk to production build outputs

* test a build

* Revert "test a build"

This reverts commit f89bfeefb7.
2024-06-03 17:32:58 -07:00
Thomas Dickerson bd4703ca1e Support for Flickr album and group pool embeds (#3936)
* Support for Flickr album and group pool embeds

* Oops, forgot to add flickr to the persisted externalEmbeds schema

* Need a bigint since our id can have more than 52 bits...

* Remove unexpected trailing / from test data to match the expected behavior

* nits

---------

Co-authored-by: Hailey <me@haileyok.com>
2024-06-03 17:29:45 -07:00
Samuel Newman 891b432ead Composer - add animated bottom border (#4325)
* start adding bottom border (wip)

* add content change listener

* add layout listener and move to hook

* remove logs

* use square-er image icon

* visually align bottom bar icons

* reduce keyboard vertical offset slightly

* only add border to top/bottom

* run worklet function on UI thread
2024-06-04 00:49:50 +01:00
dan 3b55f61d5f Avi follow experiment tweaks (#4341)
* Move avi button to visually align content

* Fix wrong prop warning

* Remove avi follow from post thread
2024-06-04 00:38:12 +01:00
Hailey 8c596b61c0 fix top border width for user list updates (#4340)
* fix nits in add/remove users from list screen

invert check

use `ViewHeader`

simplify

replace with hairline width

fix top border width for user list updates

* dont use `ViewHeader`

* update one more hairline
2024-06-03 16:34:37 -07:00
Bartosz Kaszubowski 44670c7ee2 Unify profile tabs and lists screens placeholders (#4315) 2024-06-03 16:05:26 -07:00
Hailey 8d8323421c remove resolution from post thread (#4297)
* remove resolution from post thread

nit

completely remove did cache lookup

move cache check for did to `usePostThreadQuery`

remove resolution from post thread

* helper function

* simplify

* simplify search too

* fix missing check for root or parent quoted post 🤯

* fix thread traversal
2024-06-03 23:58:16 +01:00
Yoshihiro Sugi 21d4d5f600 Fix filtering uris of fetchSubjects (#4324) 2024-06-03 23:41:03 +01:00
Hailey bda10510a4 use the new icon in reposted by (#4307)
* use the new icon in reposted by

* tweak
2024-06-03 23:35:57 +01:00
Hailey 16f295ca85 truncate if extending one line acct switcher (#4310) 2024-06-03 23:33:35 +01:00
Hailey f05aebf78e don't use flexBasis on web for message post embeds (#4303)
* don't use flexBasis on web

* rm unnecessary style
2024-06-03 17:05:37 -05:00
Eric Bailey de257a1186 Revert "[🐴] Embed backwards compat (#4302)" (#4338)
This reverts commit f868821cfc.
2024-06-03 17:05:14 -05:00
Hailey d93acb25f4 hide top border for mentions and replies (#4330) 2024-06-03 17:21:02 +01:00
Hailey 708a80e7a7 fix accessibility label in notifications (#4305)
* fix accessibility label in notifications

* add accessibility options to expand post

* inherit from outside, but always include `activate`

* include option to disable label/hint on previewable avatar

* fix hidden elements still being read on voiceover

* make it work for followers too

* extract variable

* fix hint

* update wording elsewhere
2024-05-31 21:02:18 +01:00
Samuel Newman b51640fbc0 [🐴] add emoji multiplier prop to RichText and bump it up for DMs (#4229)
* add emoji multiplier prop to RichText and bump it up for DMs

* remove background if only emoji

* Handle more emoji

* Adjust emoji regex and length

* Fix bad merge conflict res

* Fix logic

* Revert to emoji specific regex

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-05-31 20:57:42 +01:00
Eric Bailey 5cda807d9d Tweak avi follow button styles (#4304)
* Tighten up

* Tweak colors

* Tweak for night mode

* Add missing file

* Contrast plus
2024-05-31 20:45:41 +01:00
Eric Bailey f868821cfc [🐴] Embed backwards compat (#4302) 2024-05-31 12:56:06 -05:00
Eric Bailey 2bb3694819 [🐴] Add labels to chats (#4293)
* Add labels to chat list

* Add to convo header

* Prevent click through on PostAlert buttons

* Fix space

* Fix alignment
2024-05-31 12:14:11 -05:00
Samuel Newman cd3b502b34 [🐴] Option to share via chat in post dropdown (#4231)
* add send via chat button to post dropdown

(cherry picked from commit d8458c0bc344f993266f7bc7e325d47e40619648)

* let usePostQuery take uris with DIDs

(cherry picked from commit 16b577ce749fd07e1d5f8461e8ca71c5b874a936)

* add embed preview in composer

(cherry picked from commit 795ceb98d55b6a3ab5b83187a582f9656d71db69)

* rm log

(cherry picked from commit 374d6b8869459f08d8442a3a47d67149e8d9ddd4)

* remove params properly, or at least as close to

(cherry picked from commit c20e0062c2ca4d9c2b28324eee5e713a1a3ab251)

* show images in preview

(cherry picked from commit 5bb617a3ce00f67bfc79784b2f81ef8dcb5bfc25)

* Register embed immediately

(cherry picked from commit ee120d5438a2c91c8980288665576d6a29b4c7e7)

* Add hover to match embeds

(cherry picked from commit 5297a5b06e499f46a9f6da510124610005db2448)

* Update post dropdown copy

(cherry picked from commit bc7e9f6a4303926a53c5c889f1f1b136faf20491)

* Embed preview style tweaks

(cherry picked from commit 9e3ccb0f25ac2f3ce6af538bb29112a3e96e01b1)

* use hydrated posts from API and just use postembed component

(cherry picked from commit cc0b84db87ca812d76cc69f46170ae84cfdde4ef)

* fix type error

(cherry picked from commit 9c49b940e1248e8a7c3b64190c5cb20750043619)

* undo needless export

(cherry picked from commit 1186701c997c50c0b29a809637cb9bc061b8c0a0)

* fix overflow

(cherry picked from commit 8868d5075062d0199c8ef6946fabde27e46ea378)

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-05-31 11:10:00 -05:00
Samuel Newman 22e1eb18c8 [🐴] Record message (#4230)
* send record via link in text

* re-trim text after removing link

* record message

* only show copy text if message + add translate

* reduce padding

* adjust padding

* Tweak spacing

* Stop clickthrough for hidden content

* Update bg to show labels

---------

Co-authored-by: Eric Bailey <git@esb.lol>
2024-05-31 10:43:04 -05:00
Samuel Newman 8eb3cebb36 [🐴] send record via link in text (Record DMs - base PR) (#4227)
* send record via link in text

* re-trim text after removing link
2024-05-31 10:41:06 -05:00
Samuel Newman 455937dd0f Use new icons on notifications screen (#4299)
* use new icons on notif screen

* replace placeholder icon too

* cull unused legacy icons
2024-05-31 13:13:01 +01:00
Samuel Newman 05b55c1966 Composer - fix modals, and other tweaks (#4298)
* fix depreciated import

* add animations to old dropdown

* wrap modals in fullwindowoverlay

* move errors inside header

* add background to bottom bar and stop overlap

* nest dialogs on android

* fix android (wrap in gesturehandlerrootview)

* make borders all the same color

* revert threadgate button back to solid
2024-05-31 12:55:51 +01:00
Eric Bailey d614f6cb71 Shadows (#4265) 2024-05-31 05:56:09 +01:00
Hailey 89c9fd3be1 Change many border widths from 1 to hairlineWidth (#4294)
* feed items

* update some more

* moar

* profile card

* composer and notifications

* settings screen

* remove border from first item in feeds

* remove border from first item in feeds

* more removal of top border

* fix flatlist rendering

* oops

* scroll to top fab

* a.border

* centeredview/list

* placeholder

* web sidebar

* search posts

* feeds list

* user lists

* list header

* account list width 1

* hide top border feedgens

* same for lists

* fix tab bar web desktop

* wait...

* show the border on desktop web

* fix lists

* fix lists

* round
2024-05-31 05:32:54 +01:00
Samuel Newman 8569e2e389 Add follow button to feed item avatar (#3560)
* add follow button to feed item avatar

* remove confirmation

* add confirmation (just system alert)

* Shrink the avi follow indicator a smidge

* gate the follow button

* remove from your own posts

* add to post thread item

* hide the follow button locally to component

* Use native dropdown

* Add follow btn to notifications and search

* UI tweaks

* Hide on PWI

* Add toast for confirmation

* Check gate last

* compiler

* Rm unused

* Use names

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-05-31 05:05:52 +01:00
dan 9879159438 Disable non-deterministic flaky test (#4295) 2024-05-31 04:09:23 +01:00
Hailey 8e2c21a882 Don't log downsample warning when unnecessary (#4291)
* fix log

use process.env

ensure the log happens regardless of `isDownsampledSession`

fix log

* oops, revert test
2024-05-31 01:52:56 +01:00
dan d6275e98c2 [Statsig] Sample noisy events (#4288)
* Sample state:background and state:foreground

* Sample feed events

* Add DEV protection against forgetting to add events to the list
2024-05-30 16:32:59 +01:00
dan 9431201026 Bump FontAwesome (#4285) 2024-05-30 15:57:56 +01:00
dan 9d4622e713 Fix scrolling for labeler profiles (#4286) 2024-05-30 15:57:45 +01:00
dan 8de028387c Reduce Threadgate button size (#4287) 2024-05-30 15:57:03 +01:00
Samuel Newman 8feb2ab449 put dropdown in fullscreenoverlay on iOS (#4284) 2024-05-30 14:06:59 +01:00
Samuel Newman 16925baf8d play haptics before closing modal (#4283) 2024-05-30 13:46:26 +01:00
Samuel Newman b077cbe399 match loadmore position to fab (#4280) 2024-05-30 12:40:22 +01:00
Samuel Newman 3bdceac2fb Composer - Use sheet presentation on iOS (#4278)
* use sheet presentation + tweak spacing

* line up elements + add hitslop to cancel

* fixing spacing on replies
2024-05-30 12:39:36 +01:00
Hailey 76f860dad2 don't maintain position whenever there are no parents (#4277) 2024-05-30 12:36:40 +01:00
Samuel Newman 13c08f56ba Fix native translations on iOS 17.5.1 (#4282)
* enable translations on iOS 17.5.1

* add comment
2024-05-30 12:35:38 +01:00
Samuel Newman a72f55a11f Composer - fix divider when replying to someone (#4279)
* move replyto border to beneath

* use hairline width for consistency

* fix border colors
2024-05-30 12:34:30 +01:00
Samuel Newman cd497a3974 only show divider when scrolled (#4275) 2024-05-30 10:45:35 +03:00
Samuel Newman d92036f2c5 Post controls update followup (#4276)
* rm legacy repost modal

* make repost button transparent

* reduce gap between post and ctrls

* remove old repost modal on web
2024-05-30 09:44:49 +03:00
Samuel Newman c4abaa1abc Use <Modal> for Composer (#3588)
* use <Modal> to display composer

* trigger `onPressCancel` on modal cancel

* remove android top padding

* use light statusbar on ios

* use KeyboardStickyView from r-n-keyboard-controller

* make extra bottom padding ios-only

* make cancelRef optional

* scope legacy modals

* don't change bg color on ios

* use fullScreen instead of formSheet

* adjust padding on keyboardaccessory to account for new buttons

* Revert "use KeyboardStickyView from r-n-keyboard-controller"

This reverts commit 426c812904.

* fix insets

* tweaks and merge

* revert 89f51c72

* nit

* import keyboard provider

---------

Co-authored-by: Hailey <me@haileyok.com>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-05-30 05:44:20 +01:00
Samuel Newman fba4a9ca02 scale down FAB on press (#4259) 2024-05-30 05:36:07 +01:00
Paul Frazee e48f8e15eb Interpret 'hide' setting as ALWAYS hiding from thread replies (#4263) 2024-05-30 05:34:47 +01:00
Paul Frazee 5fe1e171f0 Improve the visual clarity of labels on profiles and posts (#4262)
* Update PostAlerts rendering to show the avi of the labeler rather than the display name; also add size variations

* Update ProfileHeaderAlerts to match PostAlerts behavior
2024-05-30 05:33:18 +01:00
Hailey 4cc55f05c2 Use a margin of -6 instead of -5 for PostCtrls 😵‍💫 (#4272)
* -6 instead of -5 😵‍💫

* same here
2024-05-30 04:37:45 +01:00
Hailey 9edb487949 Always show the header on post threads on native (#4254)
* always show header on native

* ALF ALF ALF

* rm offset for top border

* wrap in a `CenteredView`

* use `CenteredView`'s side borders

* account for loading state on web

* move `isTabletOrMobile`

* hide top border on first post in list

* show border if parents are loading

* don't show top border for deleted or blocked posts

* hide top border for hidden replies

* Rm root post top border

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-05-30 04:28:32 +01:00
Hailey 9628070e52 add prop to ListImpl for disabling content-visibility style (#4236)
* add prop to `ListImpl` for `content-visibility` style

* change to `disableContentVisibility`

* lint

* tweaks

* Keep the fix more general

* Clarify ambiguity

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-05-30 04:09:24 +01:00
Hailey eb6f44853d adjust notifications experiment by removing canAskAgain (#4271)
* adjust notifications experiment by removing `canAskAgain`

* move to `StepFinished` for after onboarding
2024-05-30 02:42:12 +01:00
Samuel Newman 165feedb86 Use ALF for post controls (#3400)
* alf the repost dropdown on web + import icons

* alf like icon

* convert other post controls

* add missing padding to share button

* refine buttons and use better icons

* revert buttonicon changes

* remove ButtonIcon and ButtonText from repost dialog

* use 15px font size when not big

* reduce size and use contrast_25

* add hover state to logged out view

* add `userSelect: 'none'` to buttons

* use width rather than height

* fix quote close behaviour

* prettier

* Fix Esc on repost

* Use new icons for placeholder

* Fix placeholder

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2024-05-30 01:25:11 +01:00
276 changed files with 28423 additions and 22731 deletions
+34 -1
View File
@@ -34,6 +34,9 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -96,13 +99,17 @@ jobs:
name: build-${{ steps.timestamp.outputs.time }}.apk
path: build.apk
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "Android build is ready for submission. This is a production build! Download the artifact here: ${{ steps.upload-artifact-production.outputs.artifact-url }}"
"text": "Android production build for Google Play Store submission is ready!\n```Artifact: ${{ steps.upload-artifact-production.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
@@ -120,6 +127,32 @@ jobs:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: 🏗️ Build Production APK
if: ${{ inputs.profile == 'production' }}
run: yarn use-build-number-with-bump eas build -p android --profile production-apk --local --output build.apk --non-interactive
- name: 🚀 Upload Production APK Artifact
id: upload-artifact-production-apk
if: ${{ inputs.profile == 'production' }}
uses: actions/upload-artifact@v4
with:
retention-days: 30
compression-level: 6
name: build-${{ steps.timestamp.outputs.time }}.apk
path: build.apk
- name: 🔔 Notify Slack of Production APK Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "Android production build for GitHub/Obtanium is ready!\n```Artifact: ${{ steps.upload-artifact-production-apk.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
+19
View File
@@ -34,6 +34,9 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -81,6 +84,22 @@ jobs:
- name: 🚀 Deploy
run: eas submit -p ios --non-interactive --path build.ipa
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
+15 -4
View File
@@ -36,12 +36,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn: "Edit Saved Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
id: "bottomBarHomeBtn"
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
@@ -54,11 +57,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn:
label: "Tap on down arrow"
point: "79%,23%"
- tapOn:
id: "bottomBarHomeBtn"
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "Following"
@@ -71,11 +78,15 @@ appId: xyz.blueskyweb.app
id: "viewHeaderDrawerBtn"
- tapOn:
id: "menuItemButton-Feeds"
- tapOn:
id: "editFeedsBtn"
- tapOn:
label: "Tap on unpin"
point: "91%,23%"
- tapOn:
id: "bottomBarHomeBtn"
id: "viewHeaderDrawerBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertVisible:
id: "homeScreenFeedTabs-selector-0"
text: "alice-favs"
+3 -1
View File
@@ -28,6 +28,8 @@ appId: xyz.blueskyweb.app
- tapOn: "Pin to Home"
- tapOn:
id: "bottomBarHomeBtn"
- tapOn:
id: "viewHeaderDrawerBtn"
- assertNotVisible: "Feeds ✨"
- tapOn:
@@ -50,7 +52,7 @@ appId: xyz.blueskyweb.app
text: "1"
- tapOn:
id: "repostBtn"
- tapOn: "Undo repost"
- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount"
-31
View File
@@ -1,31 +0,0 @@
# Remove this test when the old onboarding is deprecated
appId: xyz.blueskyweb.app
---
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- tapOn:
id: "e2eStartLongboarding"
- tapOn: "Continue to next step"
- tapOn: "Continue to the next step without following any accounts"
- tapOn: Show replies in Following feed
- tapOn: Show quote-posts in Following feed
- tapOn: Show re-posts in Following feed
- tapOn: Show replies in Following feed
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Continue to the next step"
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Complete onboarding and start using your account"
- waitForAnimationToEnd
- assertVisible: Following
+2 -2
View File
@@ -58,7 +58,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-bob.test"
- tapOn: "Undo repost"
- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount-expanded"
@@ -77,7 +77,7 @@ appId: xyz.blueskyweb.app
id: "repostBtn"
childOf:
id: "postThreadItem-by-carla.test"
- tapOn: "Undo repost"
- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount"
childOf:
+82 -1
View File
@@ -142,7 +142,8 @@ describe('makeRecordUri', () => {
})
})
describe('ago', () => {
// FIXME: Reenable after fixing non-deterministic test.
describe.skip('ago', () => {
const oneYearDate = new Date(
new Date().setMonth(new Date().getMonth() - 11),
).setDate(new Date().getDate() - 28)
@@ -479,6 +480,26 @@ describe('parseEmbedPlayerFromUrl', () => {
'https://media.tenor.com/someID/someName.gif',
'https://media.tenor.com/someID',
'https://media.tenor.com',
'https://www.flickr.com/photos/username/albums/72177720308493661',
'https://flickr.com/photos/username/albums/72177720308493661',
'https://flickr.com/photos/username/albums/72177720308493661/',
'https://flickr.com/photos/username/albums/72177720308493661//',
'https://flic.kr/s/aHBqjAES3i',
'https://flickr.com/foetoes/username/albums/3903',
'https://flickr.com/albums/3903',
'https://flic.kr/s/OolI',
'https://flic.kr/t/aHBqjAES3i',
'https://www.flickr.com/groups/898944@N23/pool',
'https://flickr.com/groups/898944@N23/pool',
'https://flickr.com/groups/898944@N23/pool/',
'https://flickr.com/groups/898944@N23/pool//',
'https://flic.kr/go/8WJtR',
'https://www.flickr.com/groups/898944@N23/',
'https://www.flickr.com/groups',
]
const outputs = [
@@ -776,6 +797,66 @@ describe('parseEmbedPlayerFromUrl', () => {
undefined,
undefined,
undefined,
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/photosets/72177720308493661',
},
undefined,
undefined,
undefined,
undefined,
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
{
type: 'flickr_album',
source: 'flickr',
playerUri: 'https://embedr.flickr.com/groups/898944@N23',
},
undefined,
undefined,
]
it('correctly grabs the correct id from uri', () => {
-1
View File
@@ -204,7 +204,6 @@ module.exports = function (config) {
],
'./plugins/withAndroidManifestPlugin.js',
'./plugins/withAndroidManifestFCMIconPlugin.js',
'./plugins/withAndroidManifestLaunchModePlugin.js',
'./plugins/withAndroidStylesWindowBackgroundPlugin.js',
'./plugins/withAndroidStylesAccentColorPlugin.js',
'./plugins/withAndroidSplashScreenStatusBarTranslucentPlugin.js',
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 21a1 1 0 0 1-.707-.293l-6-6a1 1 0 1 1 1.414-1.414L11 17.586V4a1 1 0 1 1 2 0v13.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-6 6A1 1 0 0 1 12 21Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 285 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M3.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7ZM14.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7Z"/></svg>

After

Width:  |  Height:  |  Size: 450 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.004 5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936A1 1 0 0 1 5.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234c.104-.085.21-.177.316-.276.846-.793 1.684-2.043 1.684-3.958V6h-5v6h2a1 1 0 0 1 1 1v4.234Zm6-12.234a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234V13a1 1 0 0 0-1-1h-2V6h5v7c0 1.915-.838 3.165-1.684 3.958-.106.1-.212.191-.316.276Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 775 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2.003 5.999a2 2 0 0 1 2-1.999h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936a1 1 0 0 1-1.351-.935L5 14H4a2 2 0 0 1-2-2.001l.002-6Zm5 11.236L7 12.999A1 1 0 0 0 6 12H4l.003-6h5v7c0 1.915-.837 3.165-1.683 3.958-.106.1-.213.192-.317.277Zm6-11.235a2 2 0 0 1 2-2h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-1a2 2 0 0 1-2-2V6Zm7 0h-5v6h2a1 1 0 0 1 1 1v4.234c.105-.085.211-.177.317-.276.846-.793 1.684-2.043 1.684-3.958V6Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 821 B

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm16 0H5v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5Zm0 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 514 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M4 5a1 1 0 0 0 0 2h16a1 1 0 1 0 0-2H4Zm0 12a1 1 0 1 0 0 2h3a1 1 0 1 0 0-2H4Zm-1-5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm14-3c.552 0 1 .41 1 .917V13.5h3.583c.507 0 .917.448.917 1s-.41 1-.917 1H18v3.583c0 .507-.448.917-1 .917s-1-.41-1-.917V15.5h-3.583c-.507 0-.917-.448-.917-1s.41-1 .917-1H16V9.917C16 9.41 16.448 9 17 9Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 471 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M2 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 299 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M8.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15C3.166 6.665 2.004 8.415 2.004 11v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5ZM19.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15-1.154 1.082-2.316 2.832-2.316 5.417v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5Z"/></svg>

After

Width:  |  Height:  |  Size: 455 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.574 4.178a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.163 8.163 0 0 1 1.569-1.15 7.029 7.029 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588a5.99 5.99 0 0 0-.316.276C4.842 7.835 4.004 9.085 4.004 11v7h5v-6h-2a1 1 0 0 1-1-1V6.766Zm12.57-2.588a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.166 8.166 0 0 1 1.569-1.15 7.038 7.038 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588c-.105.085-.21.177-.316.276-.846.793-1.684 2.043-1.684 3.958v7h5v-6h-2a1 1 0 0 1-1-1V6.766Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 857 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 412 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19c.71-2.909 3.092-5 6.322-5 .621 0 1.206.077 1.748.218a1 1 0 1 0 .504-1.936A8.931 8.931 0 0 0 12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H11a1 1 0 1 0 0-2H5.678ZM18 14a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 508 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6.5 3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3.997a6.25 6.25 0 0 0 1.83 4.42l.377.376A1 1 0 0 1 20 12.5V15a1 1 0 0 1-1 1h-6v5a1 1 0 1 1-2 0v-5H5a1 1 0 0 1-1-1v-2.5a1 1 0 0 1 .293-.707l.376-.377A6.25 6.25 0 0 0 6.5 6.996V3.001Zm2 1v2.997a8.25 8.25 0 0 1-2.416 5.834L6 12.914V14h12v-1.086l-.084-.083A8.25 8.25 0 0 1 15.5 6.997V4h-7Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 465 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H5v4a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1h13.586l-1.293-1.293a1 1 0 0 1 0-1.414ZM21 13v5a1 1 0 0 1-1 1H6.414l1.293 1.293a1 1 0 1 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 0 1 1.414 1.414L6.414 17H19v-4a1 1 0 1 1 2 0Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 446 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 517 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7 3a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2H8v2a1 1 0 1 1-2 0V8H4a1 1 0 0 1 0-2h2V4a1 1 0 0 1 1-1Zm6 4a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM3 14a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 1v4h4v-4H5Zm9.171-.829a1 1 0 0 1 1.415 0L17 15.585l1.414-1.414a1 1 0 1 1 1.414 1.414L18.414 17l1.414 1.414a1 1 0 0 1-1.414 1.414L17 18.414l-1.415 1.414a1 1 0 0 1-1.414-1.414l1.415-1.415-1.415-1.414a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 590 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" d="M12.902 1.568a1 1 0 0 0-1.804 0L8.455 7.085l-6.085.799a1 1 0 0 0-.557 1.718l4.45 4.207-1.117 6.008a1 1 0 0 0 1.458 1.063L12 17.962l5.396 2.918a1 1 0 0 0 1.459-1.063l-1.117-6.008 4.45-4.207a1 1 0 0 0-.558-1.718l-6.085-.8-2.643-5.516Z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 1a1 1 0 0 1 .902.568l2.643 5.517 6.085.799a1 1 0 0 1 .557 1.718l-4.45 4.207 1.118 6.008a1 1 0 0 1-1.46 1.063L12 17.962 6.604 20.88a1 1 0 0 1-1.459-1.063l1.117-6.008-4.45-4.207a1 1 0 0 1 .558-1.718l6.085-.8 2.643-5.516A1 1 0 0 1 12 1Zm0 3.315-1.975 4.123a1 1 0 0 1-.772.56l-4.538.595 3.317 3.137a1 1 0 0 1 .296.91l-.834 4.485 4.03-2.179a1 1 0 0 1 .952 0l4.03 2.179-.834-4.485a1 1 0 0 1 .296-.91l3.317-3.137-4.538-.596a1 1 0 0 1-.772-.56L12 4.316Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 592 B

+1
View File
@@ -207,6 +207,7 @@ func serve(cctx *cli.Context) error {
e.GET("/profile/:handleOrDID", server.WebProfile)
e.GET("/profile/:handleOrDID/follows", server.WebGeneric)
e.GET("/profile/:handleOrDID/followers", server.WebGeneric)
e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric)
e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric)
e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric)
+22 -8
View File
@@ -14,15 +14,20 @@ diff results in incompatible native changes, a new client build will automatical
### Prerequisites
- Remove any internal client from your device and download the client from the App Store/Google Play. This will help for
testing as well as retrieving the build number.
- You should have signed in to EAS locally through npx eas login. You will need to modify the build number in a
subsequent step.
- Identify the build number of the production app you want to deploy an update for. iOS and Android build numbers are
divergent, so you will need to find both
- Find the latest production build number for both iOS and Android in Slack. These are listed in #client-builds
- Production builds always send the Version Number and Build Number in the Slack message. Search for the latest
production version number, and you should find the correct information.
![slack-build-info](./img/slack-build-info.png)
- It may also be useful to check the current production clients for these values. This will also help for testing. Note
that you will need to _fully_ remove the existing internal client build from your device, otherwise the given values in
the app may differ from the actual production values.
![app-build-number](./img/app-build-number.png)
- You should have signed in to EAS locally through npx eas login. You will need to modify the build number in a
subsequent step.
- Ensure that the commit the initial client was cut from is properly tagged in git. The tag should be in the format of 1.X.0
- Note: If the commit is not properly tagged, then the OTA deployment will simply fail since the GitHub Action will
not be able to find a commit to fingerprint and diff against.
@@ -38,14 +43,17 @@ to create your branch from, this should be properly set.
### Deployment
- Update the build number through EAS
- Update the build number through EAS to match the build numbers of the
production iOS/Android apps
- Note: This isnt strictly necessary, but having a step that takes you off of GitHub and into the terminal provides
a little “friction” to avoid fat fingering a release. Since there are legitimate reasons to just “click and deploy”
for internal builds, I felt it useful to make sure it doesnt accidentally become a prod deployment.
- Set the build number to the appropriate build number found in the prerequisite steps. Again, this should be the
- Set the build numbers to the values found in the prerequisite steps. Again, this should be the
build number for the current production release you want to deploy for.
- `npx eas build:version:set -p ios`
- `npx eas build:version:set -p android`
- These steps should spit out what the current build number is, save those values
for later too
- Run the deployment
- Navigate to https://github.com/bluesky-social/social-app/actions/workflows/bundle-deploy-eas-update.yml
- Select the “Run Workflow” dropdown
@@ -79,3 +87,9 @@ In about five minutes, the new deployment should be available for download. To t
- Launch the app once and wait approximately 15 seconds
- Relaunch the app
- Check the Settings page and scroll to the bottom. The commit hash should now be the latest commit on your deployed branch.
### Post Deployment
- Reset both platforms build numbers to what they were before the OTA
deployment. These values should have been logged by the EAS CLI when you
reset them to the production values prior to OTA.
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+14
View File
@@ -46,6 +46,20 @@
"EXPO_PUBLIC_ENV": "production"
}
},
"production-apk": {
"extends": "base",
"distribution": "internal",
"ios": {
"autoIncrement": false
},
"android": {
"autoIncrement": false
},
"channel": "production",
"env": {
"EXPO_PUBLIC_ENV": "production"
}
},
"testflight": {
"extends": "base",
"ios": {
+7 -1
View File
@@ -1,4 +1,5 @@
import UserNotifications
import UIKit
let APP_GROUP = "group.app.bsky"
@@ -31,7 +32,12 @@ class NotificationService: UNNotificationServiceExtension {
}
func mutateWithBadge(_ content: UNMutableNotificationContent) {
content.badge = 1
var count = prefs?.integer(forKey: "badgeCount") ?? 0
count += 1
// Set the new badge number for the notification, then store that value for using later
content.badge = NSNumber(value: count)
prefs?.setValue(count, forKey: "badgeCount")
}
func mutateWithChatMessage(_ content: UNMutableNotificationContent) {
@@ -66,5 +66,9 @@ class ExpoBackgroundNotificationHandlerModule : Module() {
AsyncFunction("removeManyFromStringArrayAsync") { forKey: String, strings: Array<String> ->
NotificationPrefs(appContext.reactContext).removeManyFromStringArray(forKey, strings)
}
AsyncFunction("setBadgeCountAsync") { _: Int ->
// This does nothing on Android
}
}
}
@@ -10,7 +10,8 @@ let DEFAULTS: [String:Any] = [
"playSoundQuote": false,
"playSoundReply": false,
"playSoundRepost": false,
"mutedThreads": [:] as! [String:[String]]
"mutedThreads": [:] as! [String:[String]],
"badgeCount": 0,
]
/*
@@ -112,5 +113,9 @@ public class ExpoBackgroundNotificationHandlerModule: Module {
userDefaults?.setValue(curr, forKey: forKey)
}
}
AsyncFunction("setBadgeCountAsync") { (count: Int) in
userDefaults?.setValue(count, forKey: "badgeCount")
}
}
}
@@ -31,6 +31,7 @@ export type ExpoBackgroundNotificationHandlerModule = {
forKey: keyof BackgroundNotificationHandlerPreferences,
value: string[],
) => Promise<void>
setBadgeCountAsync: (count: number) => Promise<void>
}
// TODO there are more preferences in the native code, however they have not been added here yet.
@@ -24,4 +24,5 @@ export const BackgroundNotificationHandler = {
removeFromStringArrayAsync: async (_: string, __: string) => {},
addManyToStringArrayAsync: async (_: string, __: string[]) => {},
removeManyFromStringArrayAsync: async (_: string, __: string[]) => {},
setBadgeCountAsync: async (_: number) => {},
} as ExpoBackgroundNotificationHandlerModule
@@ -1,6 +0,0 @@
{
"platforms": ["ios"],
"ios": {
"modules": ["ExpoBlueskyTranslateModule"]
}
}
-6
View File
@@ -1,6 +0,0 @@
export {
isAvailable,
isLanguageSupported,
NativeTranslationModule,
NativeTranslationView,
} from './src/ExpoBlueskyTranslateView'
@@ -1,20 +0,0 @@
import ExpoModulesCore
import SwiftUI
// Thanks to Andrew Levy for this code snippet
// https://github.com/andrew-levy/swiftui-react-native/blob/d3fbb2abf07601ff0d4b83055e7717bb980910d6/ios/Common/ExpoView%2BUIHostingController.swift
extension ExpoView {
func setupHostingController(_ hostingController: UIHostingController<some View>) {
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
hostingController.view.backgroundColor = .clear
addSubview(hostingController.view)
NSLayoutConstraint.activate([
hostingController.view.topAnchor.constraint(equalTo: self.topAnchor),
hostingController.view.bottomAnchor.constraint(equalTo: self.bottomAnchor),
hostingController.view.leftAnchor.constraint(equalTo: self.leftAnchor),
hostingController.view.rightAnchor.constraint(equalTo: self.rightAnchor),
])
}
}
@@ -1,21 +0,0 @@
Pod::Spec.new do |s|
s.name = 'ExpoBlueskyTranslate'
s.version = '1.0.0'
s.summary = 'Uses SwiftUI translation to translate text.'
s.description = 'Uses SwiftUI translation to translate text.'
s.author = ''
s.homepage = 'https://docs.expo.dev/modules/'
s.platforms = { :ios => '13.4' }
s.source = { git: '' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'SWIFT_COMPILATION_MODE' => 'wholemodule'
}
s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}"
end
@@ -1,18 +0,0 @@
import ExpoModulesCore
import Foundation
import SwiftUI
public class ExpoBlueskyTranslateModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoBlueskyTranslate")
AsyncFunction("presentAsync") { (text: String) in
DispatchQueue.main.async { [weak state = TranslateViewState.shared] in
state?.isPresented = true
state?.text = text
}
}
View(ExpoBlueskyTranslateView.self) {}
}
}
@@ -1,22 +0,0 @@
import ExpoModulesCore
import Foundation
import SwiftUI
class TranslateViewState: ObservableObject {
static var shared = TranslateViewState()
@Published var isPresented = false
@Published var text = ""
}
class ExpoBlueskyTranslateView: ExpoView {
required init(appContext: AppContext? = nil) {
if #available(iOS 14.0, *) {
let hostingController = UIHostingController(rootView: TranslateView())
super.init(appContext: appContext)
setupHostingController(hostingController)
} else {
super.init(appContext: appContext)
}
}
}
@@ -1,31 +0,0 @@
import SwiftUI
// conditionally import the Translation module
#if canImport(Translation)
import Translation
#endif
struct TranslateView: View {
@ObservedObject var state = TranslateViewState.shared
var body: some View {
if #available(iOS 17.4, *) {
VStack {
UIViewRepresentableWrapper(view: UIView(frame: .zero))
}
.translationPresentation(
isPresented: $state.isPresented,
text: state.text
)
}
}
}
struct UIViewRepresentableWrapper: UIViewRepresentable {
let view: UIView
func makeUIView(context: Context) -> UIView {
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
@@ -1,3 +0,0 @@
export type ExpoBlueskyTranslateModule = {
presentAsync: (text: string) => Promise<void>
}
@@ -1,48 +0,0 @@
import React from 'react'
import {Platform} from 'react-native'
import {requireNativeModule, requireNativeViewManager} from 'expo-modules-core'
import {ExpoBlueskyTranslateModule} from './ExpoBlueskyTranslate.types'
export const NativeTranslationModule =
requireNativeModule<ExpoBlueskyTranslateModule>('ExpoBlueskyTranslate')
const NativeView: React.ComponentType = requireNativeViewManager(
'ExpoBlueskyTranslate',
)
export function NativeTranslationView() {
return <NativeView />
}
export const isAvailable = Number(Platform.Version) >= 17.4
// https://en.wikipedia.org/wiki/Translate_(Apple)#Languages
const SUPPORTED_LANGUAGES = [
'ar',
'zh',
'zh',
'nl',
'en',
'en',
'fr',
'de',
'id',
'it',
'ja',
'ko',
'pl',
'pt',
'ru',
'es',
'th',
'tr',
'uk',
'vi',
]
export function isLanguageSupported(lang?: string) {
// If the language is not provided, we assume it is supported
if (!lang) return true
return SUPPORTED_LANGUAGES.includes(lang)
}
@@ -1,13 +0,0 @@
export const NativeTranslationModule = {
presentAsync: async (_: string) => {},
}
export function NativeTranslationView() {
return null
}
export const isAvailable = false
export function isLanguageSupported(_lang?: string) {
return false
}
+6 -5
View File
@@ -1,6 +1,6 @@
{
"name": "bsky.app",
"version": "1.85.0",
"version": "1.86.0",
"private": true,
"engines": {
"node": ">=18"
@@ -49,7 +49,7 @@
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
},
"dependencies": {
"@atproto/api": "^0.12.13",
"@atproto/api": "^0.12.18",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
@@ -63,7 +63,7 @@
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-regular-svg-icons": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-native-fontawesome": "^0.3.0",
"@fortawesome/react-native-fontawesome": "^0.3.2",
"@lingui/react": "^4.5.0",
"@mattermost/react-native-paste-input": "^0.7.1",
"@miblanchard/react-native-slider": "^2.3.1",
@@ -116,7 +116,6 @@
"expo-build-properties": "^0.12.1",
"expo-camera": "~15.0.9",
"expo-clipboard": "^6.0.3",
"expo-constants": "~16.0.1",
"expo-dev-client": "^4.0.14",
"expo-device": "~6.0.2",
"expo-file-system": "^17.0.1",
@@ -129,7 +128,7 @@
"expo-localization": "~15.0.3",
"expo-media-library": "~16.0.3",
"expo-navigation-bar": "~3.0.4",
"expo-notifications": "~0.28.3",
"expo-notifications": "~0.28.7",
"expo-sharing": "^12.0.1",
"expo-splash-screen": "~0.27.4",
"expo-status-bar": "~1.12.1",
@@ -272,6 +271,8 @@
"resolutions": {
"@types/react": "^18",
"**/zeed-dom": "0.10.9",
"**/expo-constants": "16.0.1",
"**/expo-device": "6.0.2",
"@react-native/babel-preset": "0.74.1"
},
"jest": {
+5
View File
@@ -0,0 +1,5 @@
## expo-modules-core Patch
This patch fixes crashes seen in some Android clients when using intents to open the app. See https://github.com/expo/expo/pull/29513.
Do not remove this patch until that PR lands in Expo and is released.
+14
View File
@@ -0,0 +1,14 @@
diff --git a/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java b/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
index bb74e80..0aa0202 100644
--- a/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
+++ b/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
@@ -90,8 +90,8 @@ public class NativeModulesProxy extends ReactContextBaseJavaModule {
mModuleRegistry.ensureIsInitialized();
KotlinInteropModuleRegistry kotlinModuleRegistry = getKotlinInteropModuleRegistry();
- kotlinModuleRegistry.emitOnCreate();
kotlinModuleRegistry.installJSIInterop();
+ kotlinModuleRegistry.emitOnCreate();
Map<String, Object> constants = new HashMap<>(3);
constants.put(MODULES_CONSTANTS_KEY, new HashMap<>());
@@ -1,29 +1,54 @@
diff --git a/node_modules/expo-notifications/android/build.gradle b/node_modules/expo-notifications/android/build.gradle
index d233e1f..cc2f856 100644
index b863077..8b5209e 100644
--- a/node_modules/expo-notifications/android/build.gradle
+++ b/node_modules/expo-notifications/android/build.gradle
@@ -32,6 +32,7 @@ dependencies {
api 'com.google.firebase:firebase-messaging:22.0.0'
api 'me.leolin:ShortcutBadger:1.1.22@aar'
+ implementation project(':expo-background-notification-handler')
if (project.findProject(':expo-modules-test-core')) {
testImplementation project(':expo-modules-test-core')
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
index 63a46c5..f911834 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/badge/BadgeHelper.kt
@@ -1,5 +1,6 @@
package expo.modules.notifications.badge
+import android.app.NotificationManager
import android.content.Context
import android.util.Log
import me.leolin.shortcutbadger.ShortcutBadgeException
@@ -12,7 +13,12 @@ object BadgeHelper {
fun setBadgeCount(context: Context, badgeCount: Int): Boolean {
return try {
- ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
+ if (badgeCount == 0) {
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancelAll()
+ } else {
+ ShortcutBadger.applyCountOrThrow(context.applicationContext, badgeCount)
+ }
BadgeHelper.badgeCount = badgeCount
true
} catch (e: ShortcutBadgeException) {
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
index 0af7fe0..8f2c8d8 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/JSONNotificationContentBuilder.java
@@ -14,6 +14,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
import expo.modules.notifications.notifications.model.NotificationContent;
public class JSONNotificationContentBuilder extends NotificationContent.Builder {
+ private static final String CHANNEL_ID_KEY = "channelId";
private static final String TITLE_KEY = "title";
private static final String TEXT_KEY = "message";
private static final String SUBTITLE_KEY = "subtitle";
@@ -36,6 +37,7 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
public NotificationContent.Builder setPayload(JSONObject payload) {
this.setTitle(getTitle(payload))
+ .setChannelId(getChannelId(payload))
@@ -33,7 +58,7 @@ index 0af7fe0..8f2c8d8 100644
@@ -60,6 +62,14 @@ public class JSONNotificationContentBuilder extends NotificationContent.Builder
return this;
}
+ protected String getChannelId(JSONObject payload) {
+ try {
+ return payload.getString(CHANNEL_ID_KEY);
@@ -46,7 +71,7 @@ index 0af7fe0..8f2c8d8 100644
try {
return payload.getString(TITLE_KEY);
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
index f1fed19..80afe9e 100644
index f1fed19..012757b 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/model/NotificationContent.java
@@ -20,6 +20,7 @@ import expo.modules.notifications.notifications.enums.NotificationPriority;
@@ -60,10 +85,10 @@ index f1fed19..80afe9e 100644
@@ -50,6 +51,11 @@ public class NotificationContent implements Parcelable, Serializable {
}
};
+ @Nullable
+ public String getChannelId() {
+ return mTitle;
+ return mChannelId;
+ }
+
@Nullable
@@ -71,14 +96,14 @@ index f1fed19..80afe9e 100644
return mTitle;
@@ -121,6 +127,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
protected NotificationContent(Parcel in) {
+ mChannelId = in.readString();
mTitle = in.readString();
mText = in.readString();
mSubtitle = in.readString();
@@ -146,6 +153,7 @@ public class NotificationContent implements Parcelable, Serializable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mChannelId);
@@ -87,7 +112,7 @@ index f1fed19..80afe9e 100644
dest.writeString(mSubtitle);
@@ -166,6 +174,7 @@ public class NotificationContent implements Parcelable, Serializable {
private static final long serialVersionUID = 397666843266836802L;
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ out.writeObject(mChannelId);
out.writeObject(mTitle);
@@ -95,7 +120,7 @@ index f1fed19..80afe9e 100644
out.writeObject(mSubtitle);
@@ -190,6 +199,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+ mChannelId = (String) in.readObject();
mTitle = (String) in.readObject();
@@ -103,7 +128,7 @@ index f1fed19..80afe9e 100644
mSubtitle = (String) in.readObject();
@@ -240,6 +250,7 @@ public class NotificationContent implements Parcelable, Serializable {
}
public static class Builder {
+ private String mChannelId;
private String mTitle;
@@ -112,7 +137,7 @@ index f1fed19..80afe9e 100644
@@ -260,6 +271,11 @@ public class NotificationContent implements Parcelable, Serializable {
useDefaultVibrationPattern();
}
+ public Builder setChannelId(String channelId) {
+ mChannelId = channelId;
+ return this;
@@ -122,7 +147,7 @@ index f1fed19..80afe9e 100644
mTitle = title;
return this;
@@ -336,6 +352,7 @@ public class NotificationContent implements Parcelable, Serializable {
public NotificationContent build() {
NotificationContent content = new NotificationContent();
+ content.mChannelId = mChannelId;
@@ -134,16 +159,16 @@ index 6bd9928..ee93d70 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
+++ b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/notifications/presentation/builders/ExpoNotificationBuilder.java
@@ -48,6 +48,10 @@ public class ExpoNotificationBuilder extends ChannelAwareNotificationBuilder {
NotificationContent content = getNotificationContent();
+ if (content.getChannelId() != null) {
+ builder.setChannelId(content.getChannelId());
+ }
+
builder.setAutoCancel(content.isAutoDismiss());
builder.setOngoing(content.isSticky());
diff --git a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt b/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
index 55b3a8d..1b99d5b 100644
--- a/node_modules/expo-notifications/android/src/main/java/expo/modules/notifications/service/delegates/FirebaseMessagingDelegate.kt
@@ -158,7 +183,7 @@ index 55b3a8d..1b99d5b 100644
import org.json.JSONObject
import java.lang.ref.WeakReference
import java.util.*
-open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate {
+open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseMessagingDelegate, BackgroundNotificationHandlerInterface {
companion object {
@@ -166,7 +191,7 @@ index 55b3a8d..1b99d5b 100644
// than by static properties. Fortunately, using weak references we can
@@ -89,12 +92,21 @@ open class FirebaseMessagingDelegate(protected val context: Context) : FirebaseM
fun getBackgroundTasks() = sBackgroundTaskConsumerReferences.values.mapNotNull { it.get() }
override fun onMessageReceived(remoteMessage: RemoteMessage) {
- NotificationsService.receive(context, createNotification(remoteMessage))
- getBackgroundTasks().forEach {
@@ -181,7 +206,7 @@ index 55b3a8d..1b99d5b 100644
+ }
}
}
+ override fun showMessage(remoteMessage: RemoteMessage) {
+ NotificationsService.receive(context, createNotification(remoteMessage))
+ }
@@ -7,3 +7,8 @@ in `onMessageReceived` are sent to the module for handling.
It also allows us to set the Android notification channel ID from the notification `data`, rather
than the `notification` object in the payload.
### `setBadgeCountAsync` fix on Android
`ShortcutBadger`'s `setCount` doesn't work for clearing the badge on Android for some reason. Instead, let's use the
Android API for clearing the badge.
+16
View File
@@ -67,3 +67,19 @@ index b09e653..4c32b31 100644
+}
+
@end
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
index 5f5e1ab..aac00b6 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java
@@ -99,8 +99,9 @@ public class JavaTimerManager {
}
// If the JS thread is busy for multiple frames we cancel any other pending runnable.
- if (mCurrentIdleCallbackRunnable != null) {
- mCurrentIdleCallbackRunnable.cancel();
+ IdleCallbackRunnable currentRunnable = mCurrentIdleCallbackRunnable;
+ if (currentRunnable != null) {
+ currentRunnable.cancel();
}
mCurrentIdleCallbackRunnable = new IdleCallbackRunnable(frameTimeNanos);
@@ -1,17 +0,0 @@
const {withAndroidManifest} = require('expo/config-plugins')
module.exports = function withAndroidManifestLaunchModePlugin(appConfig) {
return withAndroidManifest(appConfig, function (decoratedAppConfig) {
try {
const mainApplication =
decoratedAppConfig.modResults.manifest.application[0]
const mainActivity = mainApplication.activity.find(
elem => elem.$['android:name'] === '.MainActivity',
)
mainActivity.$['android:launchMode'] = 'singleTop'
} catch (e) {
console.error(`withAndroidManifestLaunchModePlugin failed`, e)
}
return decoratedAppConfig
})
}
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
outputIos=$(eas build:version:get -p ios)
outputAndroid=$(eas build:version:get -p android)
BSKY_IOS_BUILD_NUMBER=${outputIos#*buildNumber - }
BSKY_ANDROID_VERSION_CODE=${outputAndroid#*versionCode - }
echo PACKAGE_VERSION="$(jq -r '.version' package.json)" > "$GITHUB_OUTPUT"
echo BSKY_IOS_BUILD_NUMBER=$BSKY_IOS_BUILD_NUMBER >> "$GITHUB_OUTPUT"
echo BSKY_ANDROID_VERSION_CODE=$BSKY_ANDROID_VERSION_CODE >> "$GITHUB_OUTPUT"
+1 -1
View File
@@ -143,7 +143,7 @@ function App() {
* that is set up in the InnerApp component above.
*/
return (
<KeyboardProvider enabled={true} statusBarTranslucent={true}>
<KeyboardProvider enabled={false} statusBarTranslucent={true}>
<SessionProvider>
<ShellStateProvider>
<PrefsStateProvider>
+9 -26
View File
@@ -22,7 +22,6 @@ import {buildStateObject} from 'lib/routes/helpers'
import {
AllNavigatorParams,
BottomTabNavigatorParams,
FeedsTabNavigatorParams,
FlatNavigatorParams,
HomeTabNavigatorParams,
MessagesTabNavigatorParams,
@@ -42,6 +41,7 @@ import {PreferencesThreads} from 'view/screens/PreferencesThreads'
import {SavedFeeds} from 'view/screens/SavedFeeds'
import HashtagScreen from '#/screens/Hashtag'
import {ModerationScreen} from '#/screens/Moderation'
import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {init as initAnalytics} from './lib/analytics/analytics'
import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration'
@@ -91,7 +91,6 @@ const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
const HomeTab = createNativeStackNavigatorWithAuth<HomeTabNavigatorParams>()
const SearchTab = createNativeStackNavigatorWithAuth<SearchTabNavigatorParams>()
const FeedsTab = createNativeStackNavigatorWithAuth<FeedsTabNavigatorParams>()
const NotificationsTab =
createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()
const MyProfileTab =
@@ -171,6 +170,13 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
title: title(msg`People followed by @${route.params.name}`),
})}
/>
<Stack.Screen
name="ProfileKnownFollowers"
getComponent={() => ProfileKnownFollowersScreen}
options={({route}) => ({
title: title(msg`Followers of @${route.params.name} that you know`),
})}
/>
<Stack.Screen
name="ProfileList"
getComponent={() => ProfileListScreen}
@@ -306,6 +312,7 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => MessagesSettingsScreen}
options={{title: title(msg`Chat settings`), requireAuth: true}}
/>
<Stack.Screen name="Feeds" getComponent={() => FeedsScreen} />
</>
)
}
@@ -330,7 +337,6 @@ function TabsNavigator() {
tabBar={tabBar}>
<Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} />
<Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} />
<Tab.Screen name="FeedsTab" getComponent={() => FeedsTabNavigator} />
<Tab.Screen
name="NotificationsTab"
getComponent={() => NotificationsTabNavigator}
@@ -384,24 +390,6 @@ function SearchTabNavigator() {
)
}
function FeedsTabNavigator() {
const pal = usePalette('default')
return (
<FeedsTab.Navigator
screenOptions={{
animation: isAndroid ? 'ios' : undefined,
animationDuration: 285,
gestureEnabled: true,
fullScreenGestureEnabled: true,
headerShown: false,
contentStyle: pal.view,
}}>
<FeedsTab.Screen name="Feeds" getComponent={() => FeedsScreen} />
{commonScreens(FeedsTab as typeof HomeTab)}
</FeedsTab.Navigator>
)
}
function NotificationsTabNavigator() {
const pal = usePalette('default')
return (
@@ -505,11 +493,6 @@ const FlatNavigator = () => {
getComponent={() => SearchScreen}
options={{title: title(msg`Search`)}}
/>
<Flat.Screen
name="Feeds"
getComponent={() => FeedsScreen}
options={{title: title(msg`Feeds`)}}
/>
<Flat.Screen
name="Notifications"
getComponent={() => NotificationsScreen}
+24 -7
View File
@@ -1,7 +1,8 @@
import {Platform} from 'react-native'
import {Platform, StyleSheet} from 'react-native'
import * as tokens from '#/alf/tokens'
import {native, web} from '#/alf/util/platform'
import hairlineWidth = StyleSheet.hairlineWidth
export const atoms = {
/*
@@ -55,6 +56,13 @@ export const atoms = {
height: '100vh',
}),
/*
* Theme-independent bg colors
*/
bg_transparent: {
backgroundColor: 'transparent',
},
/*
* Border radius
*/
@@ -270,19 +278,19 @@ export const atoms = {
borderWidth: 0,
},
border: {
borderWidth: 1,
borderWidth: hairlineWidth,
},
border_t: {
borderTopWidth: 1,
borderTopWidth: hairlineWidth,
},
border_b: {
borderBottomWidth: 1,
borderBottomWidth: hairlineWidth,
},
border_l: {
borderLeftWidth: 1,
borderLeftWidth: hairlineWidth,
},
border_r: {
borderRightWidth: 1,
borderRightWidth: hairlineWidth,
},
/*
@@ -841,7 +849,7 @@ export const atoms = {
marginRight: 'auto',
},
/*
* Pointer events
* Pointer events & user select
*/
pointer_events_none: {
pointerEvents: 'none',
@@ -849,6 +857,15 @@ export const atoms = {
pointer_events_auto: {
pointerEvents: 'auto',
},
user_select_none: {
userSelect: 'none',
},
user_select_text: {
userSelect: 'text',
},
user_select_all: {
userSelect: 'all',
},
/*
* Text decoration
*/
+5 -3
View File
@@ -1,12 +1,14 @@
import React from 'react'
import {Dimensions} from 'react-native'
import * as themes from '#/alf/themes'
export * from '#/alf/types'
export * as tokens from '#/alf/tokens'
export {atoms} from '#/alf/atoms'
export * from '#/alf/util/platform'
export * as tokens from '#/alf/tokens'
export * from '#/alf/types'
export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
type BreakpointName = keyof typeof breakpoints
+5 -5
View File
@@ -1,11 +1,11 @@
import {atoms} from '#/alf/atoms'
import * as tokens from '#/alf/tokens'
import type {Mutable} from '#/alf/types'
import {atoms} from '#/alf/atoms'
import {BLUE_HUE, GREEN_HUE, RED_HUE} from '#/alf/util/colorGeneration'
export type ThemeName = 'light' | 'dim' | 'dark'
export type ReadonlyTheme = typeof light
export type Theme = Mutable<ReadonlyTheme>
export type Theme = Mutable<ReadonlyTheme> & {name: ThemeName}
export type ReadonlyPalette = typeof lightPalette
export type Palette = Mutable<ReadonlyPalette>
@@ -193,7 +193,7 @@ export const dimPalette: Palette = {
} as const
export const light = {
name: 'light',
name: 'light' as ThemeName,
palette: lightPalette,
atoms: {
text: {
@@ -278,7 +278,7 @@ export const light = {
}
export const dark: Theme = {
name: 'dark',
name: 'dark' as ThemeName,
palette: darkPalette,
atoms: {
text: {
@@ -367,7 +367,7 @@ export const dark: Theme = {
export const dim: Theme = {
...dark,
name: 'dim',
name: 'dim' as ThemeName,
palette: dimPalette,
atoms: {
...dark.atoms,
+14
View File
@@ -0,0 +1,14 @@
import {ThemeName} from '#/alf/themes'
export function select<T>(name: ThemeName, options: Record<ThemeName, T>) {
switch (name) {
case 'light':
return options.light
case 'dark':
return options.dark || options.dim
case 'dim':
return options.dim || options.dark
default:
throw new Error(`select(theme, options) received unknown theme ${name}`)
}
}
+17 -15
View File
@@ -1,25 +1,27 @@
import React from 'react'
import {ColorSchemeName, useColorScheme} from 'react-native'
import {useThemePrefs} from 'state/shell'
import {isWeb} from 'platform/detection'
import {ThemeName, light, dark, dim} from '#/alf/themes'
import * as SystemUI from 'expo-system-ui'
import {isWeb} from 'platform/detection'
import {useThemePrefs} from 'state/shell'
import {dark, dim, light, ThemeName} from '#/alf/themes'
export function useColorModeTheme(): ThemeName {
const theme = useThemeName()
React.useLayoutEffect(() => {
updateDocument(theme)
SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
}, [theme])
return theme
}
export function useThemeName(): ThemeName {
const colorScheme = useColorScheme()
const {colorMode, darkTheme} = useThemePrefs()
React.useLayoutEffect(() => {
const theme = getThemeName(colorScheme, colorMode, darkTheme)
updateDocument(theme)
SystemUI.setBackgroundColorAsync(getBackgroundColor(theme))
}, [colorMode, colorScheme, darkTheme])
return React.useMemo(
() => getThemeName(colorScheme, colorMode, darkTheme),
[colorScheme, colorMode, darkTheme],
)
return getThemeName(colorScheme, colorMode, darkTheme)
}
function getThemeName(
@@ -53,7 +55,7 @@ function updateDocument(theme: ThemeName) {
}
}
function getBackgroundColor(theme: ThemeName): string {
export function getBackgroundColor(theme: ThemeName): string {
switch (theme) {
case 'light':
return light.atoms.bg.backgroundColor
+2 -2
View File
@@ -37,7 +37,7 @@ export function AccountList({
style={[
a.rounded_md,
a.overflow_hidden,
a.border,
{borderWidth: 1},
t.atoms.border_contrast_low,
]}>
{accounts.map(account => (
@@ -48,7 +48,7 @@ export function AccountList({
isCurrentAccount={account.did === currentAccount?.did}
isPendingAccount={account.did === pendingDid}
/>
<View style={[a.border_b, t.atoms.border_contrast_low]} />
<View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} />
</React.Fragment>
))}
<Button
+11 -3
View File
@@ -28,7 +28,7 @@ export type ButtonColor =
| 'gradient_sunset'
| 'gradient_nordic'
| 'gradient_bonfire'
export type ButtonSize = 'tiny' | 'small' | 'medium' | 'large'
export type ButtonSize = 'tiny' | 'xsmall' | 'small' | 'medium' | 'large'
export type ButtonShape = 'round' | 'square' | 'default'
export type VariantProps = {
/**
@@ -64,13 +64,14 @@ type NonTextElements =
export type ButtonProps = Pick<
PressableProps,
'disabled' | 'onPress' | 'testID' | 'onLongPress'
'disabled' | 'onPress' | 'testID' | 'onLongPress' | 'hitSlop'
> &
AccessibilityProps &
VariantProps & {
testID?: string
label: string
style?: StyleProp<ViewStyle>
hoverStyle?: StyleProp<ViewStyle>
children: NonTextElements | ((context: ButtonContext) => NonTextElements)
}
@@ -96,6 +97,7 @@ export function Button({
label,
disabled = false,
style,
hoverStyle: hoverStyleProp,
...rest
}: ButtonProps) {
const t = useTheme()
@@ -281,6 +283,8 @@ export function Button({
baseStyles.push({paddingVertical: 12}, a.px_2xl, a.rounded_sm, a.gap_md)
} else if (size === 'small') {
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
} else if (size === 'xsmall') {
baseStyles.push({paddingVertical: 6}, a.px_sm, a.rounded_sm, a.gap_sm)
} else if (size === 'tiny') {
baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs)
}
@@ -293,6 +297,8 @@ export function Button({
}
} else if (size === 'small') {
baseStyles.push({height: 34, width: 34})
} else if (size === 'xsmall') {
baseStyles.push({height: 28, width: 28})
} else if (size === 'tiny') {
baseStyles.push({height: 20, width: 20})
}
@@ -374,8 +380,10 @@ export function Button({
a.align_center,
a.justify_center,
flattenedBaseStyles,
...(state.hovered || state.pressed ? hoverStyles : []),
flatten(style),
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
: []),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
+44 -41
View File
@@ -32,6 +32,7 @@ import {
DialogOuterProps,
} from '#/components/Dialog/types'
import {createInput} from '#/components/forms/TextField'
import {FullWindowOverlay} from '#/components/FullWindowOverlay'
import {Portal} from '#/components/Portal'
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
@@ -170,47 +171,49 @@ export function Outer({
return (
isOpen && (
<Portal>
<View
// iOS
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
style={[a.absolute, a.inset_0]}
testID={testID}
onTouchMove={() => Keyboard.dismiss()}>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
snapPoints={sheetOptions.snapPoints || ['100%']}
ref={sheet}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={Backdrop}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onClose={onCloseAnimationComplete}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</View>
<FullWindowOverlay>
<View
// iOS
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
style={[a.absolute, a.inset_0]}
testID={testID}
onTouchMove={() => Keyboard.dismiss()}>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
snapPoints={sheetOptions.snapPoints || ['100%']}
ref={sheet}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={Backdrop}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onClose={onCloseAnimationComplete}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</View>
</FullWindowOverlay>
</Portal>
)
)
+1
View File
@@ -0,0 +1 @@
export {FullWindowOverlay} from 'react-native-screens'
+1
View File
@@ -0,0 +1 @@
export {Fragment as FullWindowOverlay} from 'react'
@@ -5,7 +5,7 @@ import Animated, {
useSharedValue,
} from 'react-native-reanimated'
export function KeyboardPadding({maxHeight}: {maxHeight?: number}) {
export function KeyboardControllerPadding({maxHeight}: {maxHeight?: number}) {
const keyboardHeight = useSharedValue(0)
useKeyboardHandler(
@@ -0,0 +1,7 @@
export function KeyboardControllerPadding({
maxHeight: _,
}: {
maxHeight?: number
}) {
return null
}
-3
View File
@@ -1,3 +0,0 @@
export function KeyboardPadding({maxHeight: _}: {maxHeight?: number}) {
return null
}
+204
View File
@@ -0,0 +1,204 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, useTheme} from '#/alf'
import {Link, LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
const AVI_SIZE = 30
const AVI_BORDER = 1
/**
* Shared logic to determine if `KnownFollowers` should be shown.
*
* Checks the # of actual returned users instead of the `count` value, because
* `count` includes blocked users and `followers` does not.
*/
export function shouldShowKnownFollowers(
knownFollowers?: AppBskyActorDefs.KnownFollowers,
) {
return knownFollowers && knownFollowers.followers.length > 0
}
export function KnownFollowers({
profile,
moderationOpts,
onLinkPress,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
onLinkPress?: LinkProps['onPress']
}) {
const cache = React.useRef<Map<string, AppBskyActorDefs.KnownFollowers>>(
new Map(),
)
/*
* Results for `knownFollowers` are not sorted consistently, so when
* revalidating we can see a flash of this data updating. This cache prevents
* this happening for screens that remain in memory. When pushing a new
* screen, or once this one is popped, this cache is empty, so new data is
* displayed.
*/
if (profile.viewer?.knownFollowers && !cache.current.has(profile.did)) {
cache.current.set(profile.did, profile.viewer.knownFollowers)
}
const cachedKnownFollowers = cache.current.get(profile.did)
if (cachedKnownFollowers && shouldShowKnownFollowers(cachedKnownFollowers)) {
return (
<KnownFollowersInner
profile={profile}
cachedKnownFollowers={cachedKnownFollowers}
moderationOpts={moderationOpts}
onLinkPress={onLinkPress}
/>
)
}
return null
}
function KnownFollowersInner({
profile,
moderationOpts,
cachedKnownFollowers,
onLinkPress,
}: {
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
cachedKnownFollowers: AppBskyActorDefs.KnownFollowers
onLinkPress?: LinkProps['onPress']
}) {
const t = useTheme()
const {_} = useLingui()
const textStyle = [
a.flex_1,
a.text_sm,
a.leading_snug,
t.atoms.text_contrast_medium,
]
const slice = cachedKnownFollowers.followers.slice(0, 3).map(f => {
const moderation = moderateProfile(f, moderationOpts)
return {
profile: {
...f,
displayName: sanitizeDisplayName(
f.displayName || f.handle,
moderation.ui('displayName'),
),
},
moderation,
}
})
const count = cachedKnownFollowers.count - Math.min(slice.length, 2)
return (
<Link
label={_(
msg`Press to view followers of this account that you also follow`,
)}
onPress={onLinkPress}
to={makeProfileLink(profile, 'known-followers')}
style={[
a.flex_1,
a.flex_row,
a.gap_md,
a.align_center,
{marginLeft: -AVI_BORDER},
]}>
{({hovered, pressed}) => (
<>
<View
style={[
{
height: AVI_SIZE,
width: AVI_SIZE + (slice.length - 1) * a.gap_md.gap,
},
pressed && {
opacity: 0.5,
},
]}>
{slice.map(({profile: prof, moderation}, i) => (
<View
key={prof.did}
style={[
a.absolute,
a.rounded_full,
{
borderWidth: AVI_BORDER,
borderColor: t.atoms.bg.backgroundColor,
width: AVI_SIZE + AVI_BORDER * 2,
height: AVI_SIZE + AVI_BORDER * 2,
left: i * a.gap_md.gap,
zIndex: AVI_BORDER - i,
},
]}>
<UserAvatar
size={AVI_SIZE}
avatar={prof.avatar}
moderation={moderation.ui('avatar')}
/>
</View>
))}
</View>
<Text
style={[
textStyle,
hovered && {
textDecorationLine: 'underline',
textDecorationColor: t.atoms.text_contrast_medium.color,
},
pressed && {
opacity: 0.5,
},
]}
numberOfLines={2}>
{count > 2 ? (
<Trans>
Followed by{' '}
<Text key={slice[0].profile.did} style={textStyle}>
{slice[0].profile.displayName}
</Text>
,{' '}
<Text key={slice[1].profile.did} style={textStyle}>
{slice[1].profile.displayName}
</Text>
, and{' '}
<Plural value={count - 2} one="# other" other="# others" />
</Trans>
) : count === 2 ? (
<Trans>
Followed by{' '}
<Text key={slice[0].profile.did} style={textStyle}>
{slice[0].profile.displayName}
</Text>{' '}
and{' '}
<Text key={slice[1].profile.did} style={textStyle}>
{slice[1].profile.displayName}
</Text>
</Trans>
) : (
<Trans>
Followed by{' '}
<Text key={slice[0].profile.did} style={textStyle}>
{slice[0].profile.displayName}
</Text>
</Trans>
)}
</Text>
</>
)}
</Link>
)
}
+3 -11
View File
@@ -12,7 +12,8 @@ import {
isExternalUrl,
linkRequiresWarning,
} from '#/lib/strings/url-helpers'
import {isNative, isWeb} from '#/platform/detection'
import {isNative} from '#/platform/detection'
import {shouldClickOpenNewTab} from '#/platform/urls'
import {useModalControls} from '#/state/modals'
import {useOpenLink} from '#/state/preferences/in-app-browser'
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
@@ -116,16 +117,7 @@ export function useLink({
if (isExternal) {
openLink(href)
} else {
/**
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
* of @ts-ignore below.
*/
const event = e as any
const isMiddleClick = isWeb && event.button === 1
const isMetaKey =
isWeb &&
(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey)
const shouldOpenInNewTab = isMetaKey || isMiddleClick
const shouldOpenInNewTab = shouldClickOpenNewTab(e)
if (isBskyDownloadUrl(href)) {
shareUrl(BSKY_DOWNLOAD_URL)
+69 -25
View File
@@ -5,6 +5,7 @@ import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom'
import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {getModerationCauseKey} from '#/lib/moderation'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
@@ -22,11 +23,16 @@ import {useFollowMethods} from '#/components/hooks/useFollowMethods'
import {useRichText} from '#/components/hooks/useRichText'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {
KnownFollowers,
shouldShowKnownFollowers,
} from '#/components/KnownFollowers'
import {InlineLinkText, Link} from '#/components/Link'
import {Loader} from '#/components/Loader'
import {Portal} from '#/components/Portal'
import {RichText} from '#/components/RichText'
import {Text} from '#/components/Typography'
import {ProfileLabel} from '../moderation/ProfileHeaderAlerts'
import {ProfileHoverCardProps} from './types'
const floatingMiddlewares = [
@@ -370,7 +376,10 @@ function Inner({
profile: profileShadow,
logContext: 'ProfileHoverCard',
})
const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy
const isBlockedUser =
profile.viewer?.blocking ||
profile.viewer?.blockedBy ||
profile.viewer?.blockingByList
const following = formatCount(profile.followsCount || 0)
const followers = formatCount(profile.followersCount || 0)
const pluralizedFollowers = plural(profile.followersCount || 0, {
@@ -401,29 +410,41 @@ function Inner({
/>
</Link>
{!isMe && (
<Button
size="small"
color={profileShadow.viewer?.following ? 'secondary' : 'primary'}
variant="solid"
label={
profileShadow.viewer?.following
? _(msg`Following`)
: _(msg`Follow`)
}
style={[a.rounded_full]}
onPress={profileShadow.viewer?.following ? unfollow : follow}>
<ButtonIcon
position="left"
icon={profileShadow.viewer?.following ? Check : Plus}
/>
<ButtonText>
{profileShadow.viewer?.following
? _(msg`Following`)
: _(msg`Follow`)}
</ButtonText>
</Button>
)}
{!isMe &&
(isBlockedUser ? (
<Link
to={profileURL}
label={_(msg`View blocked user's profile`)}
onPress={hide}
size="small"
color="secondary"
variant="solid"
style={[a.rounded_full]}>
<ButtonText>{_(msg`View profile`)}</ButtonText>
</Link>
) : (
<Button
size="small"
color={profileShadow.viewer?.following ? 'secondary' : 'primary'}
variant="solid"
label={
profileShadow.viewer?.following
? _(msg`Following`)
: _(msg`Follow`)
}
style={[a.rounded_full]}
onPress={profileShadow.viewer?.following ? unfollow : follow}>
<ButtonIcon
position="left"
icon={profileShadow.viewer?.following ? Check : Plus}
/>
<ButtonText>
{profileShadow.viewer?.following
? _(msg`Following`)
: _(msg`Follow`)}
</ButtonText>
</Button>
))}
</View>
<Link to={profileURL} label={_(msg`View profile`)} onPress={hide}>
@@ -439,7 +460,19 @@ function Inner({
</View>
</Link>
{!blockHide && (
{isBlockedUser && (
<View style={[a.flex_row, a.flex_wrap, a.gap_xs]}>
{moderation.ui('profileView').alerts.map(cause => (
<ProfileLabel
key={getModerationCauseKey(cause)}
cause={cause}
disableDetailsDialog
/>
))}
</View>
)}
{!isBlockedUser && (
<>
<View style={[a.flex_row, a.flex_wrap, a.gap_md, a.pt_xs]}>
<InlineLinkText
@@ -473,6 +506,17 @@ function Inner({
/>
</View>
) : undefined}
{!isMe &&
shouldShowKnownFollowers(profile.viewer?.knownFollowers) && (
<View style={[a.flex_row, a.align_center, a.gap_sm, a.pt_md]}>
<KnownFollowers
profile={profile}
moderationOpts={moderationOpts}
onLinkPress={hide}
/>
</View>
)}
</>
)}
</View>
@@ -15,7 +15,6 @@ import * as Dialog from '#/components/Dialog'
import * as Toggle from '#/components/forms/Toggle'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {KeyboardPadding} from '#/components/KeyboardPadding'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {ReportDialogProps} from './types'
@@ -222,7 +221,6 @@ export function SubmitView({
{submitting && <ButtonIcon icon={Loader} />}
</Button>
</View>
<KeyboardPadding />
</View>
)
}
+13 -7
View File
@@ -28,6 +28,7 @@ export function RichText({
authorHandle,
onLinkPress,
interactiveStyle,
emojiMultiplier = 1.85,
}: TextStyleProp &
Pick<TextProps, 'selectable'> & {
value: RichTextAPI | string
@@ -38,6 +39,7 @@ export function RichText({
authorHandle?: string
onLinkPress?: LinkProps['onPress']
interactiveStyle?: TextStyle
emojiMultiplier?: number
}) {
const richText = React.useMemo(
() =>
@@ -57,17 +59,14 @@ export function RichText({
const {text, facets} = richText
if (!facets?.length) {
if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) {
if (isOnlyEmoji(text)) {
const fontSize =
(flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier
return (
<Text
selectable={selectable}
testID={testID}
style={[
{
fontSize: 26,
lineHeight: 30,
},
]}
style={[plainStyles, {fontSize}]}
// @ts-ignore web only -prf
dataSet={WORD_WRAP}>
{text}
@@ -247,3 +246,10 @@ function RichTextTag({
</React.Fragment>
)
}
export function isOnlyEmoji(text: string) {
return (
text.length <= 15 &&
/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+$/u.test(text)
)
}
+255
View File
@@ -0,0 +1,255 @@
import React, {
useCallback,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import {Modal, ScrollView, TextInput, View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {cleanError} from '#/lib/strings/errors'
import {
Gif,
useFeaturedGifsQuery,
useGifSearchQuery,
} from '#/state/queries/tenor'
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
import {FlatList_INTERNAL} from '#/view/com/util/Views'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import * as TextField from '#/components/forms/TextField'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {Button, ButtonText} from '../Button'
import {Handle} from '../Dialog'
import {useThrottledValue} from '../hooks/useThrottledValue'
import {ListFooter, ListMaybePlaceholder} from '../Lists'
import {GifPreview} from './GifSelect.shared'
export function GifSelectDialog({
controlRef,
onClose,
onSelectGif: onSelectGifProp,
}: {
controlRef: React.RefObject<{open: () => void}>
onClose: () => void
onSelectGif: (gif: Gif) => void
}) {
const t = useTheme()
const [open, setOpen] = useState(false)
useImperativeHandle(controlRef, () => ({
open: () => setOpen(true),
}))
const close = useCallback(() => {
setOpen(false)
onClose()
}, [onClose])
const onSelectGif = useCallback(
(gif: Gif) => {
onSelectGifProp(gif)
close()
},
[onSelectGifProp, close],
)
const renderErrorBoundary = useCallback(
(error: any) => <ModalError details={String(error)} close={close} />,
[close],
)
return (
<Modal
visible={open}
animationType="slide"
presentationStyle="formSheet"
onRequestClose={close}
aria-modal
accessibilityViewIsModal>
<View style={[a.flex_1, t.atoms.bg]}>
<Handle />
<ErrorBoundary renderError={renderErrorBoundary}>
<GifList onSelectGif={onSelectGif} close={close} />
</ErrorBoundary>
</View>
</Modal>
)
}
function GifList({
onSelectGif,
}: {
close: () => void
onSelectGif: (gif: Gif) => void
}) {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const textInputRef = useRef<TextInput>(null)
const listRef = useRef<FlatList_INTERNAL>(null)
const [undeferredSearch, setSearch] = useState('')
const search = useThrottledValue(undeferredSearch, 500)
const isSearching = search.length > 0
const trendingQuery = useFeaturedGifsQuery()
const searchQuery = useGifSearchQuery(search)
const {
data,
fetchNextPage,
isFetchingNextPage,
hasNextPage,
error,
isLoading,
isError,
refetch,
} = isSearching ? searchQuery : trendingQuery
const flattenedData = useMemo(() => {
return data?.pages.flatMap(page => page.results) || []
}, [data])
const renderItem = useCallback(
({item}: {item: Gif}) => {
return <GifPreview gif={item} onSelectGif={onSelectGif} />
},
[onSelectGif],
)
const onEndReached = React.useCallback(() => {
if (isFetchingNextPage || !hasNextPage || error) return
fetchNextPage()
}, [isFetchingNextPage, hasNextPage, error, fetchNextPage])
const hasData = flattenedData.length > 0
const onGoBack = useCallback(() => {
if (isSearching) {
// clear the input and reset the state
textInputRef.current?.clear()
setSearch('')
} else {
close()
}
}, [isSearching])
const listHeader = useMemo(() => {
return (
<View style={[a.relative, a.mb_lg, a.pt_4xl, a.flex_row, a.align_center]}>
{/* cover top corners */}
<View
style={[
a.absolute,
a.inset_0,
{
borderBottomLeftRadius: 8,
borderBottomRightRadius: 8,
},
t.atoms.bg,
]}
/>
<TextField.Root>
<TextField.Icon icon={Search} />
<TextField.Input
label={_(msg`Search GIFs`)}
placeholder={_(msg`Search Tenor`)}
onChangeText={text => {
setSearch(text)
listRef.current?.scrollToOffset({offset: 0, animated: false})
}}
returnKeyType="search"
clearButtonMode="while-editing"
inputRef={textInputRef}
maxLength={50}
/>
</TextField.Root>
</View>
)
}, [t.atoms.bg, _])
return (
<FlatList_INTERNAL
ref={listRef}
key={gtMobile ? '3 cols' : '2 cols'}
data={flattenedData}
renderItem={renderItem}
numColumns={gtMobile ? 3 : 2}
columnWrapperStyle={a.gap_sm}
contentContainerStyle={a.px_lg}
ListHeaderComponent={
<>
{listHeader}
{!hasData && (
<ListMaybePlaceholder
isLoading={isLoading}
isError={isError}
onRetry={refetch}
onGoBack={onGoBack}
emptyType="results"
sideBorders={false}
topBorder={false}
errorTitle={_(msg`Failed to load GIFs`)}
errorMessage={_(msg`There was an issue connecting to Tenor.`)}
emptyMessage={
isSearching
? _(msg`No search results found for "${search}".`)
: _(
msg`No featured GIFs found. There may be an issue with Tenor.`,
)
}
/>
)}
</>
}
stickyHeaderIndices={[0]}
onEndReached={onEndReached}
onEndReachedThreshold={4}
keyExtractor={(item: Gif) => item.id}
keyboardDismissMode="on-drag"
ListFooterComponent={
hasData ? (
<ListFooter
isFetchingNextPage={isFetchingNextPage}
error={cleanError(error)}
onRetry={fetchNextPage}
style={{borderTopWidth: 0}}
/>
) : null
}
/>
)
}
function ModalError({details, close}: {details?: string; close: () => void}) {
const {_} = useLingui()
return (
<ScrollView
style={[a.flex_1, a.gap_md]}
centerContent
contentContainerStyle={a.px_lg}>
<ErrorScreen
title={_(msg`Oh no!`)}
message={_(
msg`There was an unexpected issue in the application. Please let us know if this happened to you!`,
)}
details={details}
/>
<Button
label={_(msg`Close dialog`)}
onPress={close}
color="primary"
size="medium"
variant="solid">
<ButtonText>
<Trans>Close</Trans>
</ButtonText>
</Button>
</ScrollView>
)
}
@@ -0,0 +1,53 @@
import React, {useCallback} from 'react'
import {Image} from 'expo-image'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig'
import {Gif} from '#/state/queries/tenor'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button} from '../Button'
export function GifPreview({
gif,
onSelectGif,
}: {
gif: Gif
onSelectGif: (gif: Gif) => void
}) {
const {gtTablet} = useBreakpoints()
const {_} = useLingui()
const t = useTheme()
const onPress = useCallback(() => {
logEvent('composer:gif:select', {})
onSelectGif(gif)
}, [onSelectGif, gif])
return (
<Button
label={_(msg`Select GIF "${gif.title}"`)}
style={[a.flex_1, gtTablet ? {maxWidth: '33%'} : {maxWidth: '50%'}]}
onPress={onPress}>
{({pressed}) => (
<Image
style={[
a.flex_1,
a.mb_sm,
a.rounded_sm,
{aspectRatio: 1, opacity: pressed ? 0.8 : 1},
t.atoms.bg_contrast_25,
]}
source={{
uri: gif.media_formats.tinygif.url,
}}
contentFit="cover"
accessibilityLabel={gif.title}
accessibilityHint=""
cachePolicy="none"
accessibilityIgnoresInvertColors
/>
)}
</Button>
)
}
+16 -49
View File
@@ -1,11 +1,15 @@
import React, {useCallback, useMemo, useRef, useState} from 'react'
import React, {
useCallback,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react'
import {TextInput, View} from 'react-native'
import {Image} from 'expo-image'
import {BottomSheetFlatListMethods} from '@discord/bottom-sheet'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {logEvent} from '#/lib/statsig/statsig'
import {cleanError} from '#/lib/strings/errors'
import {isWeb} from '#/platform/detection'
import {
@@ -23,16 +27,23 @@ import {ArrowLeft_Stroke2_Corner0_Rounded as Arrow} from '#/components/icons/Arr
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {Button, ButtonIcon, ButtonText} from '../Button'
import {ListFooter, ListMaybePlaceholder} from '../Lists'
import {GifPreview} from './GifSelect.shared'
export function GifSelectDialog({
control,
controlRef,
onClose,
onSelectGif: onSelectGifProp,
}: {
control: Dialog.DialogControlProps
controlRef: React.RefObject<{open: () => void}>
onClose: () => void
onSelectGif: (gif: Gif) => void
}) {
const control = Dialog.useDialogControl()
useImperativeHandle(controlRef, () => ({
open: () => control.open(),
}))
const onSelectGif = useCallback(
(gif: Gif) => {
control.close(() => onSelectGifProp(gif))
@@ -233,50 +244,6 @@ function GifList({
)
}
function GifPreview({
gif,
onSelectGif,
}: {
gif: Gif
onSelectGif: (gif: Gif) => void
}) {
const {gtTablet} = useBreakpoints()
const {_} = useLingui()
const t = useTheme()
const onPress = useCallback(() => {
logEvent('composer:gif:select', {})
onSelectGif(gif)
}, [onSelectGif, gif])
return (
<Button
label={_(msg`Select GIF "${gif.title}"`)}
style={[a.flex_1, gtTablet ? {maxWidth: '33%'} : {maxWidth: '50%'}]}
onPress={onPress}>
{({pressed}) => (
<Image
style={[
a.flex_1,
a.mb_sm,
a.rounded_sm,
{aspectRatio: 1, opacity: pressed ? 0.8 : 1},
t.atoms.bg_contrast_25,
]}
source={{
uri: gif.media_formats.tinygif.url,
}}
contentFit="cover"
accessibilityLabel={gif.title}
accessibilityHint=""
cachePolicy="none"
accessibilityIgnoresInvertColors
/>
)}
</Button>
)
}
function DialogError({details}: {details?: string}) {
const {_} = useLingui()
const control = Dialog.useDialogContext()
-2
View File
@@ -28,7 +28,6 @@ import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Has
import {PageText_Stroke2_Corner0_Rounded as PageText} from '#/components/icons/PageText'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {KeyboardPadding} from '#/components/KeyboardPadding'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
@@ -257,7 +256,6 @@ function MutedWordsInner() {
</View>
<Dialog.Close />
<KeyboardPadding maxHeight={100} />
</Dialog.ScrollableInner>
)
}
+5 -3
View File
@@ -26,9 +26,11 @@ import {ArrowBoxLeft_Stroke2_Corner0_Rounded as ArrowBoxLeft} from '#/components
import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag'
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheck} from '#/components/icons/PersonCheck'
import {PersonX_Stroke2_Corner0_Rounded as PersonX} from '#/components/icons/PersonX'
import {
Person_Stroke2_Corner0_Rounded as Person,
PersonCheck_Stroke2_Corner0_Rounded as PersonCheck,
PersonX_Stroke2_Corner0_Rounded as PersonX,
} from '#/components/icons/Person'
import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as Unmute} from '#/components/icons/Speaker'
import * as Menu from '#/components/Menu'
import * as Prompt from '#/components/Prompt'
+47 -33
View File
@@ -6,7 +6,11 @@ import {
TextStyle,
View,
} from 'react-native'
import {ChatBskyConvoDefs, RichText as RichTextAPI} from '@atproto/api'
import {
AppBskyEmbedRecord,
ChatBskyConvoDefs,
RichText as RichTextAPI,
} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -17,7 +21,8 @@ import {atoms as a, useTheme} from '#/alf'
import {ActionsWrapper} from '#/components/dms/ActionsWrapper'
import {InlineLinkText} from '#/components/Link'
import {Text} from '#/components/Typography'
import {RichText} from '../RichText'
import {isOnlyEmoji, RichText} from '../RichText'
import {MessageItemEmbed} from './MessageItemEmbed'
let MessageItem = ({
item,
@@ -77,37 +82,46 @@ let MessageItem = ({
return (
<View style={[isFromSelf ? a.mr_md : a.ml_md]}>
<ActionsWrapper isFromSelf={isFromSelf} message={message}>
<View
style={[
a.py_sm,
a.my_2xs,
a.rounded_md,
{
paddingLeft: 14,
paddingRight: 14,
backgroundColor: isFromSelf
? isPending
? pendingColor
: t.palette.primary_500
: t.palette.contrast_50,
borderRadius: 17,
},
isFromSelf
? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
]}>
<RichText
value={rt}
style={[
a.text_md,
a.leading_snug,
isFromSelf && {color: t.palette.white},
isPending && t.name !== 'light' && {color: t.palette.primary_300},
]}
interactiveStyle={a.underline}
enableTags
/>
</View>
{AppBskyEmbedRecord.isView(message.embed) && (
<MessageItemEmbed embed={message.embed} />
)}
{rt.text.length > 0 && (
<View
style={
!isOnlyEmoji(message.text) && [
a.py_sm,
a.my_2xs,
a.rounded_md,
{
paddingLeft: 14,
paddingRight: 14,
backgroundColor: isFromSelf
? isPending
? pendingColor
: t.palette.primary_500
: t.palette.contrast_50,
borderRadius: 17,
},
isFromSelf ? a.self_end : a.self_start,
isFromSelf
? {borderBottomRightRadius: isLastInGroup ? 2 : 17}
: {borderBottomLeftRadius: isLastInGroup ? 2 : 17},
]
}>
<RichText
value={rt}
style={[
a.text_md,
isFromSelf && {color: t.palette.white},
isPending &&
t.name !== 'light' && {color: t.palette.primary_300},
]}
interactiveStyle={a.underline}
enableTags
emojiMultiplier={3}
/>
</View>
)}
</ActionsWrapper>
{isLastInGroup && (
+22
View File
@@ -0,0 +1,22 @@
import React from 'react'
import {View} from 'react-native'
import {AppBskyEmbedRecord} from '@atproto/api'
import {PostEmbeds} from '#/view/com/util/post-embeds'
import {atoms as a, native, useTheme} from '#/alf'
let MessageItemEmbed = ({
embed,
}: {
embed: AppBskyEmbedRecord.View
}): React.ReactNode => {
const t = useTheme()
return (
<View style={[a.my_xs, t.atoms.bg, native({flexBasis: 0})]}>
<PostEmbeds embed={embed} allowNestedQuotes />
</View>
)
}
MessageItemEmbed = React.memo(MessageItemEmbed)
export {MessageItemEmbed}
+36 -11
View File
@@ -6,12 +6,16 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {richTextToString} from '#/lib/strings/rich-text-helpers'
import {getTranslatorLink} from '#/locale/helpers'
import {useLanguagePrefs} from '#/state/preferences'
import {useOpenLink} from '#/state/preferences/in-app-browser'
import {isWeb} from 'platform/detection'
import {useConvoActive} from 'state/messages/convo'
import {useSession} from 'state/session'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useTheme} from '#/alf'
import {ReportDialog} from '#/components/dms/ReportDialog'
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
import {Warning_Stroke2_Corner0_Rounded as Warning} from '#/components/icons/Warning'
@@ -35,10 +39,12 @@ export let MessageMenu = ({
const convo = useConvoActive()
const deleteControl = usePromptControl()
const reportControl = usePromptControl()
const langPrefs = useLanguagePrefs()
const openLink = useOpenLink()
const isFromSelf = message.sender?.did === currentAccount?.did
const onCopyPostText = React.useCallback(() => {
const onCopyMessage = React.useCallback(() => {
const str = richTextToString(
new RichText({
text: message.text,
@@ -51,6 +57,14 @@ export let MessageMenu = ({
Toast.show(_(msg`Copied to clipboard`))
}, [_, message.text, message.facets])
const onPressTranslateMessage = React.useCallback(() => {
const translatorUrl = getTranslatorLink(
message.text,
langPrefs.primaryLanguage,
)
openLink(translatorUrl)
}, [langPrefs.primaryLanguage, message.text, openLink])
const onDelete = React.useCallback(() => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
convo
@@ -81,16 +95,27 @@ export let MessageMenu = ({
)}
<Menu.Outer>
<Menu.Group>
<Menu.Item
testID="messageDropdownCopyBtn"
label={_(msg`Copy message text`)}
onPress={onCopyPostText}>
<Menu.ItemText>{_(msg`Copy message text`)}</Menu.ItemText>
<Menu.ItemIcon icon={ClipboardIcon} position="right" />
</Menu.Item>
</Menu.Group>
<Menu.Divider />
{message.text.length > 0 && (
<>
<Menu.Group>
<Menu.Item
testID="messageDropdownTranslateBtn"
label={_(msg`Translate`)}
onPress={onPressTranslateMessage}>
<Menu.ItemText>{_(msg`Translate`)}</Menu.ItemText>
<Menu.ItemIcon icon={Translate} position="right" />
</Menu.Item>
<Menu.Item
testID="messageDropdownCopyBtn"
label={_(msg`Copy message text`)}
onPress={onCopyMessage}>
<Menu.ItemText>{_(msg`Copy message text`)}</Menu.ItemText>
<Menu.ItemIcon icon={ClipboardIcon} position="right" />
</Menu.Item>
</Menu.Group>
<Menu.Divider />
</>
)}
<Menu.Group>
<Menu.Item
testID="messageDropdownDeleteBtn"
+64 -43
View File
@@ -22,6 +22,7 @@ import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {ConvoMenu} from '#/components/dms/ConvoMenu'
import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2'
import {Link} from '#/components/Link'
import {PostAlerts} from '#/components/moderation/PostAlerts'
import {Text} from '#/components/Typography'
const PFP_SIZE = isWeb ? 40 : 34
@@ -58,7 +59,7 @@ export let MessagesListHeader = ({
t.atoms.border_contrast_low,
a.border_b,
a.flex_row,
a.align_center,
a.align_start,
a.gap_sm,
gtTablet ? a.pl_lg : a.pl_xl,
a.pr_lg,
@@ -69,7 +70,7 @@ export let MessagesListHeader = ({
testID="conversationHeaderBackBtn"
onPress={onPressBack}
hitSlop={BACK_HITSLOP}
style={{width: 30, height: 30}}
style={{width: 30, height: 30, marginTop: isWeb ? 6 : 4}}
accessibilityRole="button"
accessibilityLabel={_(msg`Back`)}
accessibilityHint="">
@@ -152,51 +153,71 @@ function HeaderReady({
)
return (
<>
<Link
style={[a.flex_row, a.align_center, a.gap_md, a.flex_1, a.pr_md]}
to={makeProfileLink(profile)}>
<PreviewableUserAvatar
size={PFP_SIZE}
profile={profile}
moderation={moderation.ui('avatar')}
disableHoverCard={moderation.blocked}
/>
<View style={a.flex_1}>
<Text
style={[a.text_md, a.font_bold, web(a.leading_normal)]}
numberOfLines={1}>
{displayName}
</Text>
{!isDeletedAccount && (
<View style={[a.flex_1]}>
<View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
<Link
style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]}
to={makeProfileLink(profile)}>
<View style={[a.pt_2xs]}>
<PreviewableUserAvatar
size={PFP_SIZE}
profile={profile}
moderation={moderation.ui('avatar')}
disableHoverCard={moderation.blocked}
/>
</View>
<View style={a.flex_1}>
<Text
style={[
t.atoms.text_contrast_medium,
a.text_sm,
web([a.leading_normal, {marginTop: -2}]),
]}
style={[a.text_md, a.font_bold, web(a.leading_normal)]}
numberOfLines={1}>
@{profile.handle}
{convoState.convo?.muted && (
<>
{' '}
&middot;{' '}
<BellStroke size="xs" style={t.atoms.text_contrast_medium} />
</>
)}
{displayName}
</Text>
)}
</View>
</Link>
{!isDeletedAccount && (
<Text
style={[
t.atoms.text_contrast_medium,
a.text_sm,
web([a.leading_normal, {marginTop: -2}]),
]}
numberOfLines={1}>
@{profile.handle}
{convoState.convo?.muted && (
<>
{' '}
&middot;{' '}
<BellStroke
size="xs"
style={t.atoms.text_contrast_medium}
/>
</>
)}
</Text>
)}
</View>
</Link>
{isConvoActive(convoState) && (
<ConvoMenu
convo={convoState.convo}
profile={profile}
currentScreen="conversation"
blockInfo={blockInfo}
{isConvoActive(convoState) && (
<ConvoMenu
convo={convoState.convo}
profile={profile}
currentScreen="conversation"
blockInfo={blockInfo}
/>
)}
</View>
<View
style={[
{
paddingLeft: PFP_SIZE + a.gap_md.gap,
},
]}>
<PostAlerts
modui={moderation.ui('contentList')}
size="large"
style={[a.pt_xs]}
/>
)}
</>
</View>
</View>
)
}
+2
View File
@@ -16,6 +16,7 @@ import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {KeyboardControllerPadding} from '#/components/KeyboardControllerPadding'
import {Button, ButtonIcon, ButtonText} from '../Button'
import {Divider} from '../Divider'
import {ChevronLeft_Stroke2_Corner0_Rounded as Chevron} from '../icons/Chevron'
@@ -47,6 +48,7 @@ let ReportDialog = ({
<Dialog.ScrollableInner label={_(msg`Report this message`)}>
<DialogInner params={params} />
<Dialog.Close />
<KeyboardControllerPadding />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
@@ -0,0 +1,67 @@
import React, {useCallback} from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
import {logEvent} from 'lib/statsig/statsig'
import {FAB} from '#/view/com/util/fab/FAB'
import * as Toast from '#/view/com/util/Toast'
import {useTheme} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {SearchablePeopleList} from './SearchablePeopleList'
export function NewChat({
control,
onNewChat,
}: {
control: Dialog.DialogControlProps
onNewChat: (chatId: string) => void
}) {
const t = useTheme()
const {_} = useLingui()
const {mutate: createChat} = useGetConvoForMembers({
onSuccess: data => {
onNewChat(data.convo.id)
if (!data.convo.lastMessage) {
logEvent('chat:create', {logContext: 'NewChatDialog'})
}
logEvent('chat:open', {logContext: 'NewChatDialog'})
},
onError: error => {
Toast.show(error.message)
},
})
const onCreateChat = useCallback(
(did: string) => {
control.close(() => createChat([did]))
},
[control, createChat],
)
return (
<>
<FAB
testID="newChatFAB"
onPress={control.open}
icon={<Plus size="lg" fill={t.palette.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New chat`)}
accessibilityHint=""
/>
<Dialog.Outer
control={control}
testID="newChatDialog"
nativeOptions={{sheet: {snapPoints: ['100%']}}}>
<SearchablePeopleList
title={_(msg`Start a new chat`)}
onSelectChat={onCreateChat}
/>
</Dialog.Outer>
</>
)
}
@@ -16,23 +16,19 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
import {useListConvosQuery} from '#/state/queries/messages/list-converations'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useSession} from '#/state/session'
import {logEvent} from 'lib/statsig/statsig'
import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete'
import {FAB} from '#/view/com/util/fab/FAB'
import * as Toast from '#/view/com/util/Toast'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {atoms as a, native, useTheme, web} from '#/alf'
import {Button} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {TextInput} from '#/components/dms/NewChatDialog/TextInput'
import {TextInput} from '#/components/dms/dialogs/TextInput'
import {canBeMessaged} from '#/components/dms/util'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
import {Text} from '#/components/Typography'
@@ -57,55 +53,291 @@ type Item =
key: string
}
export function NewChat({
control,
onNewChat,
export function SearchablePeopleList({
title,
onSelectChat,
showRecentConvos,
}: {
control: Dialog.DialogControlProps
onNewChat: (chatId: string) => void
title: string
onSelectChat: (did: string) => void
showRecentConvos?: boolean
}) {
const t = useTheme()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const control = Dialog.useDialogContext()
const listRef = useRef<BottomSheetFlatListMethods>(null)
const {currentAccount} = useSession()
const inputRef = useRef<TextInputType>(null)
const {mutate: createChat} = useGetConvoForMembers({
onSuccess: data => {
onNewChat(data.convo.id)
const [searchText, setSearchText] = useState('')
if (!data.convo.lastMessage) {
logEvent('chat:create', {logContext: 'NewChatDialog'})
const {
data: results,
isError,
isFetching,
} = useActorAutocompleteQuery(searchText, true, 12)
const {data: follows} = useProfileFollowsQuery(currentAccount?.did)
const {data: convos} = useListConvosQuery({enabled: showRecentConvos})
const items = useMemo(() => {
let _items: Item[] = []
if (isError) {
_items.push({
type: 'empty',
key: 'empty',
message: _(msg`We're having network issues, try again`),
})
} else if (searchText.length) {
if (results?.length) {
for (const profile of results) {
if (profile.did === currentAccount?.did) continue
_items.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
_items = _items.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
}
logEvent('chat:open', {logContext: 'NewChatDialog'})
},
onError: error => {
Toast.show(error.message)
},
})
} else {
const placeholders: Item[] = Array(10)
.fill(0)
.map((_, i) => ({
type: 'placeholder',
key: i + '',
}))
const onCreateChat = useCallback(
(did: string) => {
control.close(() => createChat([did]))
if (showRecentConvos) {
if (convos && follows) {
const usedDids = new Set()
for (const page of convos.pages) {
for (const convo of page.convos) {
const profiles = convo.members.filter(
m => m.did !== currentAccount?.did,
)
for (const profile of profiles) {
if (usedDids.has(profile.did)) continue
usedDids.add(profile.did)
_items.push({
type: 'profile',
key: profile.did,
enabled: true,
profile,
})
}
}
}
let followsItems: typeof _items = []
for (const page of follows.pages) {
for (const profile of page.follows) {
if (usedDids.has(profile.did)) continue
followsItems.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
}
// only sort follows
followsItems = followsItems.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
// then append
_items.push(...followsItems)
} else {
_items.push(...placeholders)
}
} else if (follows) {
for (const page of follows.pages) {
for (const profile of page.follows) {
_items.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
}
_items = _items.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
} else {
_items.push(...placeholders)
}
}
return _items
}, [
_,
searchText,
results,
isError,
currentAccount?.did,
follows,
convos,
showRecentConvos,
])
if (searchText && !isFetching && !items.length && !isError) {
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
}
const renderItems = useCallback(
({item}: {item: Item}) => {
switch (item.type) {
case 'profile': {
return (
<ProfileCard
key={item.key}
enabled={item.enabled}
profile={item.profile}
moderationOpts={moderationOpts!}
onPress={onSelectChat}
/>
)
}
case 'placeholder': {
return <ProfileCardSkeleton key={item.key} />
}
case 'empty': {
return <Empty key={item.key} message={item.message} />
}
default:
return null
}
},
[control, createChat],
[moderationOpts, onSelectChat],
)
return (
<>
<FAB
testID="newChatFAB"
onPress={control.open}
icon={<Plus size="lg" fill={t.palette.white} />}
accessibilityRole="button"
accessibilityLabel={_(msg`New chat`)}
accessibilityHint=""
/>
useLayoutEffect(() => {
if (isWeb) {
setImmediate(() => {
inputRef?.current?.focus()
})
}
}, [])
<Dialog.Outer
control={control}
testID="newChatDialog"
nativeOptions={{sheet: {snapPoints: ['100%']}}}>
<SearchablePeopleList onCreateChat={onCreateChat} />
</Dialog.Outer>
</>
const listHeader = useMemo(() => {
return (
<View
style={[
a.relative,
a.pt_md,
a.pb_xs,
a.px_lg,
a.border_b,
t.atoms.border_contrast_low,
t.atoms.bg,
native([a.pt_lg]),
]}>
<View
style={[
a.relative,
native(a.align_center),
a.justify_center,
{height: 32},
]}>
<Button
label={_(msg`Close`)}
size="small"
shape="round"
variant="ghost"
color="secondary"
style={[
a.absolute,
a.z_20,
native({
left: -7,
}),
web({
right: -4,
}),
]}
onPress={() => control.close()}>
{isWeb ? (
<X size="md" fill={t.palette.contrast_500} />
) : (
<ChevronLeft size="md" fill={t.palette.contrast_500} />
)}
</Button>
<Text
style={[
a.z_10,
a.text_lg,
a.font_bold,
a.leading_tight,
t.atoms.text_contrast_high,
]}>
{title}
</Text>
</View>
<View style={[native([a.pt_sm]), web([a.pt_xs])]}>
<SearchInput
inputRef={inputRef}
value={searchText}
onChangeText={text => {
setSearchText(text)
listRef.current?.scrollToOffset({offset: 0, animated: false})
}}
onEscape={control.close}
/>
</View>
</View>
)
}, [
t.atoms.border_contrast_low,
t.atoms.bg,
t.atoms.text_contrast_high,
t.palette.contrast_500,
_,
title,
searchText,
control,
])
return (
<Dialog.InnerFlatList
ref={listRef}
data={items}
renderItem={renderItems}
ListHeaderComponent={listHeader}
stickyHeaderIndices={[0]}
keyExtractor={(item: Item) => item.key}
style={[
web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
native({
height: '100%',
paddingHorizontal: 0,
marginTop: 0,
paddingTop: 0,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
}),
]}
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
keyboardDismissMode="on-drag"
/>
)
}
@@ -163,11 +395,13 @@ function ProfileCard({
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
style={[t.atoms.text, a.font_bold, a.leading_snug]}
style={[t.atoms.text, a.font_bold, a.leading_tight]}
numberOfLines={1}>
{displayName}
</Text>
<Text style={t.atoms.text_contrast_high} numberOfLines={2}>
<Text
style={[a.leading_tight, t.atoms.text_contrast_high]}
numberOfLines={2}>
{!enabled ? <Trans>{handle} can't be messaged</Trans> : handle}
</Text>
</View>
@@ -293,217 +527,3 @@ function SearchInput({
</View>
)
}
function SearchablePeopleList({
onCreateChat,
}: {
onCreateChat: (did: string) => void
}) {
const t = useTheme()
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const control = Dialog.useDialogContext()
const listRef = useRef<BottomSheetFlatListMethods>(null)
const {currentAccount} = useSession()
const inputRef = useRef<TextInputType>(null)
const [searchText, setSearchText] = useState('')
const {
data: results,
isError,
isFetching,
} = useActorAutocompleteQuery(searchText, true, 12)
const {data: follows} = useProfileFollowsQuery(currentAccount?.did)
const items = useMemo(() => {
let _items: Item[] = []
if (isError) {
_items.push({
type: 'empty',
key: 'empty',
message: _(msg`We're having network issues, try again`),
})
} else if (searchText.length) {
if (results?.length) {
for (const profile of results) {
if (profile.did === currentAccount?.did) continue
_items.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
_items = _items.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
}
} else {
if (follows) {
for (const page of follows.pages) {
for (const profile of page.follows) {
_items.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
}
_items = _items.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
} else {
Array(10)
.fill(0)
.forEach((_, i) => {
_items.push({
type: 'placeholder',
key: i + '',
})
})
}
}
return _items
}, [_, searchText, results, isError, currentAccount?.did, follows])
if (searchText && !isFetching && !items.length && !isError) {
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
}
const renderItems = useCallback(
({item}: {item: Item}) => {
switch (item.type) {
case 'profile': {
return (
<ProfileCard
key={item.key}
enabled={item.enabled}
profile={item.profile}
moderationOpts={moderationOpts!}
onPress={onCreateChat}
/>
)
}
case 'placeholder': {
return <ProfileCardSkeleton key={item.key} />
}
case 'empty': {
return <Empty key={item.key} message={item.message} />
}
default:
return null
}
},
[moderationOpts, onCreateChat],
)
useLayoutEffect(() => {
if (isWeb) {
setImmediate(() => {
inputRef?.current?.focus()
})
}
}, [])
const listHeader = useMemo(() => {
return (
<View
style={[
a.relative,
a.pt_md,
a.pb_xs,
a.px_lg,
a.border_b,
t.atoms.border_contrast_low,
t.atoms.bg,
native([a.pt_lg]),
]}>
<View
style={[
a.relative,
native(a.align_center),
a.justify_center,
{height: 32},
]}>
<Button
label={_(msg`Close`)}
size="small"
shape="round"
variant="ghost"
color="secondary"
style={[
a.absolute,
a.z_20,
native({
left: -7,
}),
web({
right: -4,
}),
]}
onPress={() => control.close()}>
{isWeb ? (
<X size="md" fill={t.palette.contrast_500} />
) : (
<ChevronLeft size="md" fill={t.palette.contrast_500} />
)}
</Button>
<Text
style={[
a.z_10,
a.text_lg,
a.font_bold,
a.leading_tight,
t.atoms.text_contrast_high,
]}>
<Trans>Start a new chat</Trans>
</Text>
</View>
<View style={[native([a.pt_sm]), web([a.pt_xs])]}>
<SearchInput
inputRef={inputRef}
value={searchText}
onChangeText={text => {
setSearchText(text)
listRef.current?.scrollToOffset({offset: 0, animated: false})
}}
onEscape={control.close}
/>
</View>
</View>
)
}, [t, _, control, searchText])
return (
<Dialog.InnerFlatList
ref={listRef}
data={items}
renderItem={renderItems}
ListHeaderComponent={listHeader}
stickyHeaderIndices={[0]}
keyExtractor={(item: Item) => item.key}
style={[
web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
native({
height: '100%',
paddingHorizontal: 0,
marginTop: 0,
paddingTop: 0,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
}),
]}
webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
keyboardDismissMode="on-drag"
/>
)
}
@@ -0,0 +1,64 @@
import React, {useCallback} from 'react'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
import {logEvent} from 'lib/statsig/statsig'
import * as Toast from '#/view/com/util/Toast'
import * as Dialog from '#/components/Dialog'
import {SearchablePeopleList} from './SearchablePeopleList'
export function SendViaChatDialog({
control,
onSelectChat,
}: {
control: Dialog.DialogControlProps
onSelectChat: (chatId: string) => void
}) {
return (
<Dialog.Outer
control={control}
testID="sendViaChatChatDialog"
nativeOptions={{sheet: {snapPoints: ['100%']}}}>
<SendViaChatDialogInner control={control} onSelectChat={onSelectChat} />
</Dialog.Outer>
)
}
function SendViaChatDialogInner({
control,
onSelectChat,
}: {
control: Dialog.DialogControlProps
onSelectChat: (chatId: string) => void
}) {
const {_} = useLingui()
const {mutate: createChat} = useGetConvoForMembers({
onSuccess: data => {
onSelectChat(data.convo.id)
if (!data.convo.lastMessage) {
logEvent('chat:create', {logContext: 'SendViaChatDialog'})
}
logEvent('chat:open', {logContext: 'SendViaChatDialog'})
},
onError: error => {
Toast.show(error.message)
},
})
const onCreateChat = useCallback(
(did: string) => {
control.close(() => createChat([did]))
},
[control, createChat],
)
return (
<SearchablePeopleList
title={_(msg`Send post to...`)}
onSelectChat={onCreateChat}
showRecentConvos
/>
)
}
+4
View File
@@ -7,3 +7,7 @@ export const ArrowTopRight_Stroke2_Corner0_Rounded = createSinglePathSVG({
export const ArrowLeft_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3 12a1 1 0 0 1 .293-.707l6-6a1 1 0 0 1 1.414 1.414L6.414 11H20a1 1 0 1 1 0 2H6.414l4.293 4.293a1 1 0 0 1-1.414 1.414l-6-6A1 1 0 0 1 3 12Z',
})
export const ArrowBottom_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 21a1 1 0 0 1-.707-.293l-6-6a1 1 0 1 1 1.414-1.414L11 17.586V4a1 1 0 1 1 2 0v13.586l4.293-4.293a1 1 0 0 1 1.414 1.414l-6 6A1 1 0 0 1 12 21Z',
})
+1 -1
View File
@@ -1,5 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Image_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5Zm16 0H5v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5Zm0 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z',
path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v7.213l1.246-.932.044-.03a3 3 0 0 1 3.863.454c1.468 1.58 2.941 2.749 4.847 2.749 1.703 0 2.855-.555 4-1.618V5H5Zm14 10.357c-1.112.697-2.386 1.097-4 1.097-2.81 0-4.796-1.755-6.313-3.388a1 1 0 0 0-1.269-.164L5 14.712V19h14v-3.643ZM15 8a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm-3 1a3 3 0 1 1 6 0 3 3 0 0 1-6 0Z',
})
+9
View File
@@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
/*
* This icon is off-menu, not part of the icon set
*/
export const ListPlus_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M4 5a1 1 0 0 0 0 2h16a1 1 0 1 0 0-2H4Zm0 12a1 1 0 1 0 0 2h3a1 1 0 1 0 0-2H4Zm-1-5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1Zm14-3c.552 0 1 .41 1 .917V13.5h3.583c.507 0 .917.448.917 1s-.41 1-.917 1H18v3.583c0 .507-.448.917-1 .917s-1-.41-1-.917V15.5h-3.583c-.507 0-.917-.448-.917-1s.41-1 .917-1H16V9.917C16 9.41 16.448 9 17 9Z',
})
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Menu_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M2 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm0 6a1 1 0 0 1 1-1h18a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Z',
})
+16
View File
@@ -3,3 +3,19 @@ import {createSinglePathSVG} from './TEMPLATE'
export const Person_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19h12.644c-.71-2.909-3.092-5-6.322-5s-5.613 2.091-6.322 5Zm-2.174.906C3.917 15.521 7.242 12 12 12c4.758 0 8.083 3.521 8.496 7.906A1 1 0 0 1 19.5 21h-15a1 1 0 0 1-.996-1.094Z',
})
export const PersonCheck_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5a6.69 6.69 0 0 1 2.612.51 1 1 0 0 0 .776-1.844A8.687 8.687 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H11a1 1 0 1 0 0-2H5.679Zm14.835-4.857a1 1 0 0 1 .344 1.371l-3 5a1 1 0 0 1-1.458.286l-2-1.5a1 1 0 0 1 1.2-1.6l1.113.835 2.43-4.05a1 1 0 0 1 1.372-.342Z',
})
export const PersonX_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5 .302 0 .595.018.878.053a1 1 0 0 0 .243-1.985A9.235 9.235 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H12a1 1 0 1 0 0-2H5.679Zm9.614-3.707a1 1 0 0 1 1.414 0L18 16.586l1.293-1.293a1 1 0 0 1 1.414 1.414L19.414 18l1.293 1.293a1 1 0 0 1-1.414 1.414L18 19.414l-1.293 1.293a1 1 0 0 1-1.414-1.414L16.586 18l-1.293-1.293a1 1 0 0 1 0-1.414Z',
})
export const PersonPlus_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.678 19c.71-2.909 3.092-5 6.322-5 .621 0 1.206.077 1.748.218a1 1 0 1 0 .504-1.936A8.931 8.931 0 0 0 12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H11a1 1 0 1 0 0-2H5.678ZM18 14a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z',
})
export const PersonPlus_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM12 12c-4.758 0-8.083 3.521-8.496 7.906A1 1 0 0 0 4.5 21H15a3 3 0 1 1 0-6c0-.824.332-1.571.87-2.113C14.739 12.32 13.435 12 12 12Zm6 2a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2h-2v2a1 1 0 1 1-2 0v-2h-2a1 1 0 1 1 0-2h2v-2a1 1 0 0 1 1-1Z',
})
-5
View File
@@ -1,5 +0,0 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PersonCheck_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5a6.69 6.69 0 0 1 2.612.51 1 1 0 0 0 .776-1.844A8.687 8.687 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H11a1 1 0 1 0 0-2H5.679Zm14.835-4.857a1 1 0 0 1 .344 1.371l-3 5a1 1 0 0 1-1.458.286l-2-1.5a1 1 0 0 1 1.2-1.6l1.113.835 2.43-4.05a1 1 0 0 1 1.372-.342Z',
})
-5
View File
@@ -1,5 +0,0 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PersonX_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5 .302 0 .595.018.878.053a1 1 0 0 0 .243-1.985A9.235 9.235 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H12a1 1 0 1 0 0-2H5.679Zm9.614-3.707a1 1 0 0 1 1.414 0L18 16.586l1.293-1.293a1 1 0 0 1 1.414 1.414L19.414 18l1.293 1.293a1 1 0 0 1-1.414 1.414L18 19.414l-1.293 1.293a1 1 0 0 1-1.414-1.414L16.586 18l-1.293-1.293a1 1 0 0 1 0-1.414Z',
})
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Pin_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M6.5 3a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v3.997a6.25 6.25 0 0 0 1.83 4.42l.377.376A1 1 0 0 1 20 12.5V15a1 1 0 0 1-1 1h-6v5a1 1 0 1 1-2 0v-5H5a1 1 0 0 1-1-1v-2.5a1 1 0 0 1 .293-.707l.376-.377A6.25 6.25 0 0 0 6.5 6.996V3.001Zm2 1v2.997a8.25 8.25 0 0 1-2.416 5.834L6 12.914V14h12v-1.086l-.084-.083A8.25 8.25 0 0 1 15.5 6.997V4h-7Z',
})
+21
View File
@@ -0,0 +1,21 @@
import {createSinglePathSVG} from './TEMPLATE'
export const OpenQuote_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M7.574 4.178a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.163 8.163 0 0 1 1.569-1.15 7.029 7.029 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588a5.99 5.99 0 0 0-.316.276C4.842 7.835 4.004 9.085 4.004 11v7h5v-6h-2a1 1 0 0 1-1-1V6.766Zm12.57-2.588a1 1 0 0 1 .43.822v5h2a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1h-7a1 1 0 0 1-1-1v-8c0-2.585 1.162-4.335 2.316-5.417a8.166 8.166 0 0 1 1.569-1.15 7.038 7.038 0 0 1 .738-.36l.016-.005.005-.003h.003v-.001c.001 0 .002 0 .353.936l-.351-.936a1 1 0 0 1 .92.114Zm-1.57 2.588c-.105.085-.21.177-.316.276-.846.793-1.684 2.043-1.684 3.958v7h5v-6h-2a1 1 0 0 1-1-1V6.766Z',
})
export const OpenQuote_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M8.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15C3.166 6.665 2.004 8.415 2.004 11v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5ZM19.004 5a1 1 0 0 0-.43-.822c-.57-.395-1.176-.031-1.685.255-.428.24-.998.614-1.569 1.15-1.154 1.082-2.316 2.832-2.316 5.417v8a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1v-8a1 1 0 0 0-1-1h-2V5Z',
})
export const CloseQuote_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M2.004 5a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936A1 1 0 0 1 5.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234c.104-.085.21-.177.316-.276.846-.793 1.684-2.043 1.684-3.958V6h-5v6h2a1 1 0 0 1 1 1v4.234Zm6-12.234a1 1 0 0 1 1-1h7a1 1 0 0 1 1 1v8c0 2.585-1.162 4.335-2.316 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-2a1 1 0 0 1-1-1V5Zm5 12.234V13a1 1 0 0 0-1-1h-2V6h5v7c0 1.915-.838 3.165-1.684 3.958-.106.1-.212.191-.316.276Z',
})
export const CloseQuote_Stroke2_Corner1_Rounded = createSinglePathSVG({
path: 'M2.003 5.999a2 2 0 0 1 2-1.999h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.01 7.01 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001L6.004 19l.351.936a1 1 0 0 1-1.351-.935L5 14H4a2 2 0 0 1-2-2.001l.002-6Zm5 11.236L7 12.999A1 1 0 0 0 6 12H4l.003-6h5v7c0 1.915-.837 3.165-1.683 3.958-.106.1-.213.192-.317.277Zm6-11.235a2 2 0 0 1 2-2h5c1.104 0 2 .893 2 1.999V13c0 2.585-1.16 4.335-2.315 5.417-.571.536-1.14.91-1.569 1.15a7.018 7.018 0 0 1-.738.36l-.016.006-.006.002h-.002l-.001.001-.352-.936.351.936A1 1 0 0 1 16.004 19v-5h-1a2 2 0 0 1-2-2V6Zm7 0h-5v6h2a1 1 0 0 1 1 1v4.234c.105-.085.211-.177.317-.276.846-.793 1.684-2.043 1.684-3.958V6Z',
})
export const CloseQuote_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7ZM14.004 4a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2v5a1 1 0 0 0 .43.822c.57.395 1.176.031 1.685-.255.428-.24.998-.614 1.569-1.15 1.154-1.082 2.316-2.832 2.316-5.417V5a1 1 0 0 0-1-1h-7Z',
})
+13
View File
@@ -0,0 +1,13 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Repost_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M16.293 2.293a1 1 0 0 1 1.414 0l3 3a1 1 0 0 1 0 1.414l-3 3a1 1 0 0 1-1.414-1.414L17.586 7H5v4a1 1 0 1 1-2 0V6a1 1 0 0 1 1-1h13.586l-1.293-1.293a1 1 0 0 1 0-1.414ZM21 13v5a1 1 0 0 1-1 1H6.414l1.293 1.293a1 1 0 1 1-1.414 1.414l-3-3a1 1 0 0 1 0-1.414l3-3a1 1 0 0 1 1.414 1.414L6.414 17H19v-4a1 1 0 1 1 2 0Z',
})
export const Repost_Stroke2_Corner2_Rounded = createSinglePathSVG({
path: 'M17.957 2.293a1 1 0 1 0-1.414 1.414L17.836 5H6a3 3 0 0 0-3 3v3a1 1 0 1 0 2 0V8a1 1 0 0 1 1-1h11.836l-1.293 1.293a1 1 0 0 0 1.414 1.414l2.47-2.47a1.75 1.75 0 0 0 0-2.474l-2.47-2.47ZM20 12a1 1 0 0 1 1 1v3a3 3 0 0 1-3 3H6.164l1.293 1.293a1 1 0 1 1-1.414 1.414l-2.47-2.47a1.75 1.75 0 0 1 0-2.474l2.47-2.47a1 1 0 0 1 1.414 1.414L6.164 17H18a1 1 0 0 0 1-1v-3a1 1 0 0 1 1-1Z',
})
export const Repost_Stroke2_Corner3_Rounded = createSinglePathSVG({
path: 'M16.793 2.293a1 1 0 0 1 1.414 0L20.5 4.586a2 2 0 0 1 0 2.828l-2.293 2.293a1 1 0 0 1-1.414-1.414L18.086 7H7a2 2 0 0 0-2 2v2a1 1 0 1 1-2 0V9a4 4 0 0 1 4-4h11.086l-1.293-1.293a1 1 0 0 1 0-1.414ZM20 12a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H5.914l1.293 1.293a1 1 0 1 1-1.414 1.414L3.5 19.414a2 2 0 0 1 0-2.828l2.293-2.293a1 1 0 0 1 1.414 1.414L5.914 17H17a2 2 0 0 0 2-2v-2a1 1 0 0 1 1-1Z',
})
+5
View File
@@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Shapes_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M7 3a1 1 0 0 1 1 1v2h2a1 1 0 1 1 0 2H8v2a1 1 0 1 1-2 0V8H4a1 1 0 0 1 0-2h2V4a1 1 0 0 1 1-1Zm6 4a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM3 14a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-6Zm2 1v4h4v-4H5Zm9.171-.829a1 1 0 0 1 1.415 0L17 15.585l1.414-1.414a1 1 0 1 1 1.414 1.414L18.414 17l1.414 1.414a1 1 0 0 1-1.414 1.414L17 18.414l-1.415 1.414a1 1 0 0 1-1.414-1.414l1.415-1.415-1.415-1.414a1 1 0 0 1 0-1.414Z',
})
+9
View File
@@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Star_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 1a1 1 0 0 1 .902.568l2.643 5.517 6.085.799a1 1 0 0 1 .557 1.718l-4.45 4.207 1.118 6.008a1 1 0 0 1-1.46 1.063L12 17.962 6.604 20.88a1 1 0 0 1-1.459-1.063l1.117-6.008-4.45-4.207a1 1 0 0 1 .558-1.718l6.085-.8 2.643-5.516A1 1 0 0 1 12 1Zm0 3.315-1.975 4.123a1 1 0 0 1-.772.56l-4.538.595 3.317 3.137a1 1 0 0 1 .296.91l-.834 4.485 4.03-2.179a1 1 0 0 1 .952 0l4.03 2.179-.834-4.485a1 1 0 0 1 .296-.91l3.317-3.137-4.538-.596a1 1 0 0 1-.772-.56L12 4.316Z',
})
export const Star_Filled_Corner0_Rounded = createSinglePathSVG({
path: 'M12.902 1.568a1 1 0 0 0-1.804 0L8.455 7.085l-6.085.799a1 1 0 0 0-.557 1.718l4.45 4.207-1.117 6.008a1 1 0 0 0 1.458 1.063L12 17.962l5.396 2.918a1 1 0 0 0 1.459-1.063l-1.117-6.008 4.45-4.207a1 1 0 0 0-.558-1.718l-6.085-.8-2.643-5.516Z',
})
+1 -1
View File
@@ -1,5 +1,5 @@
import {StyleSheet, TextProps} from 'react-native'
import type {SvgProps, PathProps} from 'react-native-svg'
import type {PathProps, SvgProps} from 'react-native-svg'
import {tokens} from '#/alf'

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