Compare commits
144 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 397335be53 | |||
| 21b82fa19c | |||
| 7a08d61d88 | |||
| 01c9ac0e13 | |||
| 4c31403330 | |||
| 68bb451051 | |||
| 193954d401 | |||
| 1b69ecb57a | |||
| 3b68dfeb3b | |||
| d5f8d8224f | |||
| aa6aad652e | |||
| d85dcc3dd0 | |||
| 41452de165 | |||
| 1e32327de0 | |||
| 6f4703e814 | |||
| 9a82d20bd9 | |||
| e9f6a8e58d | |||
| 21ba1fad55 | |||
| 1fc684aaef | |||
| a22685c345 | |||
| 0ae64a1e0a | |||
| aa07ed6174 | |||
| c92f6d5f88 | |||
| d74aaf904b | |||
| ba802eb0f2 | |||
| c8f264b78b | |||
| ab492cd77a | |||
| 339f45ccbb | |||
| 1cfcffd79e | |||
| 27ff85433b | |||
| 96ba6456e7 | |||
| bab44a5a13 | |||
| 3bf91eb814 | |||
| 9c27d83d4b | |||
| d1355d52fc | |||
| 80c5f23da2 | |||
| 2502c91dee | |||
| b4fcbad643 | |||
| 7942c684bf | |||
| 39381e60e7 | |||
| 5e947e5a8f | |||
| 5e18b6f09f | |||
| 30e0cc2430 | |||
| 6764da47fa | |||
| 32f62acf40 | |||
| ad97ea8d24 | |||
| eadef58d82 | |||
| 7147c9bc42 | |||
| bdb5722640 | |||
| 93a53162e8 | |||
| 7755a167ec | |||
| 0689360b8b | |||
| 1b1eb3e962 | |||
| 498b83ae79 | |||
| cd6fc13e4b | |||
| 3244e1c468 | |||
| 84db1f997b | |||
| 0f3fa06c3e | |||
| d050a78a2d | |||
| dcc5405e8a | |||
| 5bce043e1e | |||
| 2e3844c7b4 | |||
| 8650d17958 | |||
| e1796d7afd | |||
| d19c505d81 | |||
| 1c992beff6 | |||
| e652b2d7b1 | |||
| 2b53ef2541 | |||
| e05b4a910f | |||
| d520dd95b9 | |||
| a729efc3b6 | |||
| 52f05424bf | |||
| 0ecc98d0de | |||
| 3327c47957 | |||
| e8a53dcea8 | |||
| fa4cca11fe | |||
| 090ac041c3 | |||
| f6649e22a7 | |||
| 24679d5fe3 | |||
| 1e723f177f | |||
| 72270fd505 | |||
| fd2e94f353 | |||
| 92ef756943 | |||
| 05ca979518 | |||
| e9be8f4574 | |||
| 0d5af050d3 | |||
| 7ca1789d6f | |||
| 834beac58c | |||
| 1c6cee7a0d | |||
| c5e40d6695 | |||
| be725f79ba | |||
| c3e5107882 | |||
| 59f9cd916e | |||
| 74b0929e4d | |||
| 9a91746fbb | |||
| 444c6e6a9f | |||
| 22e1914f58 | |||
| 34f1e4d71c | |||
| 7e4ecc6860 | |||
| eaba68ee20 | |||
| 2e74f98392 | |||
| 3d9663db1e | |||
| 21542c4a77 | |||
| 6bc00f8d71 | |||
| c3d0cc55d9 | |||
| fe5eb507ca | |||
| 53b6d16b51 | |||
| 4c3c10d7f8 | |||
| 2d88463453 | |||
| 0f40013963 | |||
| ef5bc5243e | |||
| 240535fd8b | |||
| 0b4dc64c63 | |||
| a445489b53 | |||
| db7b875c52 | |||
| 432dc867f8 | |||
| 830b4bee9c | |||
| 7f3b5366e6 | |||
| 8311a7a3d4 | |||
| b75c9d2ef6 | |||
| f40b8ac80c | |||
| d6322477fe | |||
| 8e16427497 | |||
| eaba658419 | |||
| 0f7cd2b74d | |||
| 907b8d17f5 | |||
| e53b972957 | |||
| 157011efe3 | |||
| f7852d02ba | |||
| 7e5c522718 | |||
| 8c6384175c | |||
| 5ebb630c9d | |||
| 9a769b9a03 | |||
| 7677bb3f0a | |||
| a8fb8dc6b2 | |||
| f9b01e9eac | |||
| 5da0bcaec2 | |||
| 34d3e2b127 | |||
| 9176a8ff64 | |||
| 32bf38940d | |||
| 88f326b37f | |||
| 34a0007679 | |||
| 2a3f9b38e6 | |||
| 2fc1f9ca65 |
@@ -23,7 +23,16 @@ module.exports = {
|
||||
'bsky-internal/avoid-unwrapped-text': [
|
||||
'error',
|
||||
{
|
||||
impliedTextComponents: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'],
|
||||
impliedTextComponents: [
|
||||
'H1',
|
||||
'H2',
|
||||
'H3',
|
||||
'H4',
|
||||
'H5',
|
||||
'H6',
|
||||
'P',
|
||||
'Admonition',
|
||||
],
|
||||
impliedTextProps: [],
|
||||
suggestedTextWrappers: {
|
||||
Button: 'ButtonText',
|
||||
@@ -33,9 +42,9 @@ module.exports = {
|
||||
],
|
||||
'bsky-internal/use-exact-imports': 'error',
|
||||
'bsky-internal/use-typed-gates': 'error',
|
||||
'bsky-internal/use-prefixed-imports': 'warn',
|
||||
'bsky-internal/use-prefixed-imports': 'error',
|
||||
'simple-import-sort/imports': [
|
||||
'warn',
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
// Side effect imports.
|
||||
@@ -69,9 +78,8 @@ module.exports = {
|
||||
],
|
||||
},
|
||||
],
|
||||
'simple-import-sort/exports': 'warn',
|
||||
// TODO: Reenable when we figure out why it gets stuck on CI.
|
||||
// 'react-compiler/react-compiler': 'error',
|
||||
'simple-import-sort/exports': 'error',
|
||||
'react-compiler/react-compiler': 'warn',
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
|
||||
@@ -10,6 +10,8 @@ appId: xyz.blueskyweb.app
|
||||
id: "e2eSignInAlice"
|
||||
|
||||
# Post an image with the porn label
|
||||
- assertVisible:
|
||||
id: "composeFAB"
|
||||
- tapOn:
|
||||
id: "composeFAB"
|
||||
- inputText: "Post with an image"
|
||||
@@ -17,14 +19,12 @@ appId: xyz.blueskyweb.app
|
||||
id: "openGalleryBtn"
|
||||
- tapOn:
|
||||
id: "labelsBtn"
|
||||
- tapOn:
|
||||
label: "Tap on porn"
|
||||
point: 78%,67%
|
||||
- tapOn: "Porn"
|
||||
- tapOn:
|
||||
label: "Tap on confirm"
|
||||
point: 51%,92%
|
||||
id: "confirmBtn"
|
||||
- tapOn:
|
||||
id: "composerPublishBtn"
|
||||
- tapOn:
|
||||
id: "e2eRefreshHome"
|
||||
- assertVisible: "Adult Content"
|
||||
- assertVisible: "Adult Content"
|
||||
|
||||
@@ -8,6 +8,8 @@ appId: xyz.blueskyweb.app
|
||||
file: ../setupApp.yml
|
||||
- tapOn:
|
||||
id: "e2eSignInAlice"
|
||||
- assertVisible:
|
||||
id: "composeFAB"
|
||||
- tapOn:
|
||||
id: "composeFAB"
|
||||
- inputText: "Post text only"
|
||||
@@ -60,7 +62,7 @@ appId: xyz.blueskyweb.app
|
||||
id: "repostBtn"
|
||||
- tapOn:
|
||||
id: "quoteBtn"
|
||||
- inputText: "QP text only"
|
||||
- inputText: "QP text only"
|
||||
- tapOn:
|
||||
id: "composerPublishBtn"
|
||||
- assertVisible:
|
||||
|
||||
@@ -29,6 +29,7 @@ appId: xyz.blueskyweb.app
|
||||
- pressKey: Enter
|
||||
- tapOn:
|
||||
id: "nextBtn"
|
||||
- tapOn: "Not now"
|
||||
- tapOn:
|
||||
id: "handleInput"
|
||||
- inputText: "e2e-test"
|
||||
|
||||
@@ -23,7 +23,6 @@ appId: xyz.blueskyweb.app
|
||||
id: "editDescriptionInput"
|
||||
- inputText: "They good"
|
||||
- tapOn: "Save"
|
||||
- tapOn: "Save"
|
||||
- assertNotVisible:
|
||||
id: "createOrEditListModal"
|
||||
- tapOn: "About"
|
||||
@@ -46,7 +45,6 @@ appId: xyz.blueskyweb.app
|
||||
- eraseText
|
||||
- inputText: "They bad"
|
||||
- tapOn: "Save"
|
||||
- tapOn: "Save"
|
||||
- assertNotVisible:
|
||||
id: "createOrEditListModal"
|
||||
- assertVisible: Bad Ppl
|
||||
@@ -62,7 +60,6 @@ appId: xyz.blueskyweb.app
|
||||
id: "editDescriptionInput"
|
||||
- eraseText
|
||||
- tapOn: "Save"
|
||||
- tapOn: "Save"
|
||||
- assertNotVisible:
|
||||
id: "createOrEditListModal"
|
||||
- assertNotVisible:
|
||||
@@ -90,7 +87,6 @@ appId: xyz.blueskyweb.app
|
||||
id: "editDescriptionInput"
|
||||
- inputText: "They good"
|
||||
- tapOn: "Save"
|
||||
- tapOn: "Save"
|
||||
- assertNotVisible:
|
||||
id: "createOrEditListModal"
|
||||
- tapOn: "About"
|
||||
|
||||
@@ -36,13 +36,14 @@ appId: xyz.blueskyweb.app
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- tapOn:
|
||||
id: "menuItemButton-Feeds"
|
||||
- tapOn:
|
||||
- tapOn:
|
||||
id: "editFeedsBtn"
|
||||
- tapOn:
|
||||
label: "Tap on down arrow"
|
||||
point: "79%,23%"
|
||||
id: "feed-timeline-moveDown"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
label: "Save button"
|
||||
id: "saveChangesBtn"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- assertVisible:
|
||||
@@ -57,13 +58,14 @@ appId: xyz.blueskyweb.app
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- tapOn:
|
||||
id: "menuItemButton-Feeds"
|
||||
- tapOn:
|
||||
- tapOn:
|
||||
id: "editFeedsBtn"
|
||||
- tapOn:
|
||||
label: "Tap on down arrow"
|
||||
point: "79%,23%"
|
||||
id: "feed-feed-moveDown"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
label: "Save button"
|
||||
id: "saveChangesBtn"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- assertVisible:
|
||||
@@ -78,13 +80,14 @@ appId: xyz.blueskyweb.app
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- tapOn:
|
||||
id: "menuItemButton-Feeds"
|
||||
- tapOn:
|
||||
- tapOn:
|
||||
id: "editFeedsBtn"
|
||||
- tapOn:
|
||||
label: "Tap on unpin"
|
||||
point: "91%,23%"
|
||||
id: "feed-timeline-togglePin"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
label: "Save button"
|
||||
id: "saveChangesBtn"
|
||||
- tapOn:
|
||||
id: "viewHeaderDrawerBtn"
|
||||
- assertVisible:
|
||||
|
||||
@@ -9,7 +9,7 @@ appId: xyz.blueskyweb.app
|
||||
- tapOn:
|
||||
id: "e2eOpenLoggedOutView"
|
||||
- tapOn: "Sign in"
|
||||
- tapOn:
|
||||
- tapOn:
|
||||
id: "selectServiceButton"
|
||||
- tapOn: "Custom"
|
||||
- tapOn:
|
||||
@@ -23,4 +23,5 @@ appId: xyz.blueskyweb.app
|
||||
id: "loginPasswordInput"
|
||||
- inputText: "hunter2"
|
||||
- pressKey: Enter
|
||||
- assertVisible: "Following"
|
||||
- tapOn: "Not now"
|
||||
- assertVisible: "Following"
|
||||
|
||||
@@ -22,24 +22,25 @@ appId: xyz.blueskyweb.app
|
||||
id: "editDescriptionInput"
|
||||
- inputText: "Shhh"
|
||||
- tapOn: "Save"
|
||||
- tapOn: "Save"
|
||||
|
||||
# view modlist
|
||||
- assertVisible: "Muted Users"
|
||||
- assertVisible: "Shhh"
|
||||
|
||||
# toggle mute subscription
|
||||
- tapOn:
|
||||
point: "70%,9%"
|
||||
label: "Dropdown"
|
||||
point: "71%,9%"
|
||||
|
||||
- tapOn: "Mute accounts"
|
||||
- tapOn: "Mute list"
|
||||
- tapOn: "Unmute"
|
||||
|
||||
# toggle block subscription
|
||||
- tapOn:
|
||||
point: "70%,9%"
|
||||
label: "Dropdown"
|
||||
point: "71%,9%"
|
||||
|
||||
- tapOn: "Block accounts"
|
||||
- tapOn: "Block list"
|
||||
- tapOn: "Unblock"
|
||||
|
||||
# the rest of the behaviors are tested in curate-lists.yml
|
||||
|
||||
# the rest of the behaviors are tested in curate-lists.yml
|
||||
|
||||
@@ -12,6 +12,7 @@ appId: xyz.blueskyweb.app
|
||||
# Navigate to another user profile via autocomplete
|
||||
- tapOn:
|
||||
id: "bottomBarSearchBtn"
|
||||
- assertVisible: "Search"
|
||||
- tapOn: "Search"
|
||||
- inputText: "b"
|
||||
- tapOn:
|
||||
|
||||
@@ -8,7 +8,10 @@ appId: xyz.blueskyweb.app
|
||||
file: ../setupApp.yml
|
||||
- tapOn:
|
||||
id: "e2eSignInAlice"
|
||||
- tapOn: "/sys/debug"
|
||||
- assertVisible:
|
||||
id: "storybookBtn"
|
||||
- tapOn:
|
||||
id: "storybookBtn"
|
||||
- tapOn:
|
||||
id: "sharedPrefsTestOpenBtn"
|
||||
- tapOn:
|
||||
@@ -16,7 +19,7 @@ appId: xyz.blueskyweb.app
|
||||
- assertVisible: "Hello"
|
||||
- tapOn:
|
||||
id: "removeStringBtn"
|
||||
- assertVisible: "null"
|
||||
- assertVisible: "undefined"
|
||||
- tapOn:
|
||||
id: "setBoolBtn"
|
||||
- assertVisible: "true"
|
||||
|
||||
@@ -7,10 +7,11 @@ appId: xyz.blueskyweb.app
|
||||
- runFlow:
|
||||
file: ../setupApp.yml
|
||||
|
||||
|
||||
# Login, create a thread, and log out
|
||||
- tapOn:
|
||||
id: "e2eSignInAlice"
|
||||
- assertVisible:
|
||||
id: "composeFAB"
|
||||
- tapOn:
|
||||
id: "composeFAB"
|
||||
- inputText: "Test thread"
|
||||
|
||||
@@ -236,6 +236,13 @@ module.exports = function (config) {
|
||||
fonts: [
|
||||
'./assets/fonts/inter/InterVariable.ttf',
|
||||
'./assets/fonts/inter/InterVariable-Italic.ttf',
|
||||
// Android only
|
||||
'./assets/fonts/inter/Inter-Regular.otf',
|
||||
'./assets/fonts/inter/Inter-Italic.otf',
|
||||
'./assets/fonts/inter/Inter-SemiBold.otf',
|
||||
'./assets/fonts/inter/Inter-SemiBoldItalic.otf',
|
||||
'./assets/fonts/inter/Inter-ExtraBold.otf',
|
||||
'./assets/fonts/inter/Inter-ExtraBoldItalic.otf',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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="M13.5 4a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM8 6a1 1 0 0 0 0 2v2.64q-.319.374-.711.8l-.129.142c-.312.342-.649.711-.974 1.092-.731.857-1.488 1.866-1.89 2.99A4.9 4.9 0 0 0 4 17.298 4.7 4.7 0 0 0 8.702 22h6.596A4.7 4.7 0 0 0 20 17.298a4.8 4.8 0 0 0-.297-1.634c-.401-1.124-1.157-2.133-1.89-2.99-.324-.38-.66-.75-.973-1.092h0l-.129-.141c-.26-.286-.5-.55-.711-.8V8a1 1 0 1 0 0-2H8Zm2 5.35V8h4v3.35l.22.275c.306.383.661.777 1.013 1.163l.13.143h0c.315.345.628.688.93 1.042.372.435.704.861.974 1.28l-.159.025c-.845.13-1.838.242-2.581.222-.842-.022-1.475-.217-2.227-.454l-.027-.008c-.746-.235-1.61-.507-2.746-.538-.743-.02-1.617.064-2.38.165q.26-.342.56-.692c.302-.354.615-.697.93-1.042l.13-.143c.352-.386.707-.78 1.014-1.163L10 11.35Zm7.41 5.905q.316-.048.586-.095.004.07.004.138A2.7 2.7 0 0 1 15.298 20H8.702A2.7 2.7 0 0 1 6 17.298q0-.213.039-.434c.236-.043.53-.093.853-.142.845-.13 1.837-.242 2.581-.222.842.022 1.475.217 2.227.454l.027.008c.746.235 1.61.507 2.746.538.931.024 2.07-.113 2.937-.245Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 524 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.018 6a1 1 0 0 0-.808.412l-.81-.588.809.588L3 10.825V17a1 1 0 1 0 2 0 1 1 0 0 1 1-1h12a1 1 0 0 1 1 1 1 1 0 1 0 2 0v-5.998l-3.22-4.577A1 1 0 0 0 16.962 6H7.018ZM23 11.686V17a3 3 0 0 1-5.83 1H6.83A3.001 3.001 0 0 1 1 17v-5.5a1 1 0 1 1 0-2h.49l3.102-4.265A3 3 0 0 1 7.018 4h9.944a3 3 0 0 1 2.453 1.274l3.104 4.412H23a1 1 0 1 1 0 2ZM5 13a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 578 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="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z" clip-rule="evenodd"/><path fill="#000" d="M13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm10-3a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Zm1 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 612 B After Width: | Height: | Size: 502 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="M14.243 3.03a1 1 0 0 1 .727 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414l-2.586 2.586a1 1 0 0 0 0 1.414l2.586 2.586a1 1 0 1 1-1.414 1.414l-2.586-2.586a3 3 0 0 1 0-4.242l2.586-2.586a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l2.586 2.586a3 3 0 0 1 0 4.242l-2.586 2.586a1 1 0 1 1-1.414-1.414l2.586-2.586a1 1 0 0 0 0-1.414l-2.586-2.586a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 540 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 5a1 1 0 0 1 1-1h10a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm15 0a1 1 0 0 1 1-1h3a1 1 0 1 1 0 2h-3a1 1 0 0 1-1-1ZM2 12a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2h-8a1 1 0 0 1-1-1ZM2 19a1 1 0 0 1 1-1h7a1 1 0 1 1 0 2H3a1 1 0 0 1-1-1Zm12 0a1 1 0 0 1 1-1h6a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 458 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.4 9.493C4.14 10.28 4 11.124 4 12a8 8 0 1 0 10.899-7.459l-.67 2.679a2.95 2.95 0 0 1-2.14 2.142l-2.173.547a.32.32 0 0 0-.205.164 2.316 2.316 0 0 1-3.457.81L4.4 9.493Zm.883-1.84 2.171 1.63a.315.315 0 0 0 .471-.11c.303-.6.851-1.04 1.503-1.204l2.174-.546a.95.95 0 0 0 .687-.688l.97.242-.97-.242.67-2.678a7.99 7.99 0 0 0-7.676 3.597ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm8.048.543a2.2 2.2 0 0 1 1.69-.636l.827.053c.52.033 1.023.204 1.456.495l1.37.921a2.453 2.453 0 0 1-1.367 4.489h-.98a2.95 2.95 0 0 1-2.45-1.312L9.77 15.32a2.2 2.2 0 0 1 .278-2.776Zm1.563 1.36a.197.197 0 0 0-.177.306l.823 1.235c.176.263.471.42.787.42h.98a.453.453 0 0 0 .252-.828l-1.37-.921a.95.95 0 0 0-.468-.159l-.827-.053Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 867 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="M8.789 2.293a1 1 0 0 1 1.414 0l1.793 1.793 1.793-1.793a1 1 0 1 1 1.414 1.414l-2.207 2.207v4.355l3.772-2.178.808-3.015a1 1 0 1 1 1.931.518l-.656 2.449 2.45.656a1 1 0 1 1-.518 1.932l-3.015-.808L13.998 12l3.77 2.177 3.015-.808a1 1 0 1 1 .517 1.932l-2.449.656.657 2.45a1 1 0 1 1-1.932.517l-.808-3.015-3.772-2.178v4.355l2.207 2.207a1 1 0 0 1-1.414 1.414l-1.793-1.793-1.793 1.793a1 1 0 0 1-1.414-1.414l2.207-2.207v-4.353l-3.77 2.176-.807 3.015a1 1 0 0 1-1.932-.518l.656-2.449-2.449-.656a1 1 0 1 1 .518-1.932l3.015.808L9.997 12l-3.77-2.177-3.015.808a1 1 0 0 1-.518-1.932l2.45-.656-.657-2.45a1 1 0 0 1 1.932-.517l.808 3.015 3.77 2.176V5.914L8.788 3.707a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 806 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="M9 5a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1H9ZM6 6a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H9a3 3 0 0 1-3-3V6Zm4 1a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Zm-5.793.293a1 1 0 0 1 0 1.414L3.155 9.76a.546.546 0 0 0-.05.713 2.55 2.55 0 0 1 0 3.056.546.546 0 0 0 .05.713l1.052 1.052a1 1 0 1 1-1.414 1.414L1.74 15.655a2.546 2.546 0 0 1-.237-3.327.55.55 0 0 0 0-.655 2.546 2.546 0 0 1 .237-3.328l1.052-1.052a1 1 0 0 1 1.414 0Zm15.586 0a1 1 0 0 1 1.414 0l1.052 1.052c.896.896.997 2.314.237 3.327a.55.55 0 0 0 0 .656 2.546 2.546 0 0 1-.237 3.327l-1.052 1.052a1 1 0 0 1-1.414-1.414l1.052-1.052a.546.546 0 0 0 .05-.713 2.55 2.55 0 0 1 0-3.056.546.546 0 0 0-.05-.713l-1.052-1.052a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 865 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.659 3.905a1 1 0 0 0-1.318 0l-6 5.25A1 1 0 0 0 5 9.907V18a1 1 0 0 0 1 1h3v-3a3 3 0 0 1 6 0v3h3a1 1 0 0 0 1-1V9.907a1 1 0 0 0-.341-.752l-6-5.25ZM10.024 2.4a3 3 0 0 1 3.952 0l6 5.25A3 3 0 0 1 21 9.907V18a3 3 0 0 1-3 3h-3a2 2 0 0 1-2-2v-3a1 1 0 0 0-2 0v3a2 2 0 0 1-2 2H6a3 3 0 0 1-3-3V9.907A3 3 0 0 1 4.024 7.65l6-5.25Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 462 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="M3 12a4 4 0 0 1 7.212-2.385c.363.488.963.885 1.696.885h8.111l1.2 1.5-1.2 1.5h-1.783l-1.789-.894a1 1 0 0 0-.894 0l-1.79.894h-1.855c-.733 0-1.333.397-1.696.885A4 4 0 0 1 3 12Zm4-6a6 6 0 1 0 4.817 9.579.3.3 0 0 1 .076-.072l.017-.007H14a1 1 0 0 0 .447-.106L16 14.618l1.553.776c.139.07.292.106.447.106h2.02a2 2 0 0 0 1.561-.75l1.2-1.5a2 2 0 0 0 0-2.5l-1.2-1.5a2 2 0 0 0-1.562-.75h-8.11l-.016-.007a.3.3 0 0 1-.077-.071A6 6 0 0 0 7 6Zm0 7.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 613 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 5a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v11a3 3 0 0 1-1 2.236V20a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-1.764c-.614-.55-1-1.348-1-2.236V5Zm3 14v1h10v-1H7ZM7 4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7Zm0 2a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V6Zm2 1v4h6V7H9Zm4 8a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 489 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="M1 6.5A2.5 2.5 0 0 1 3.5 4H9a4 4 0 0 1 3 1.354A4 4 0 0 1 15 4h5.5A2.5 2.5 0 0 1 23 6.5v11a2.5 2.5 0 0 1-2.5 2.5h-5.223c-.52 0-1 .125-1.4.372-.421.26-.761.633-.983 1.075a1 1 0 0 1-1.788 0 2.66 2.66 0 0 0-.983-1.075c-.4-.247-.88-.372-1.4-.372H3.5A2.5 2.5 0 0 1 1 17.5v-11ZM11 8a2 2 0 0 0-2-2H3.5a.5.5 0 0 0-.5.5v11a.5.5 0 0 0 .5.5h5.223c.776 0 1.564.173 2.277.569V8Zm2 10.569A4.7 4.7 0 0 1 15.277 18H20.5a.5.5 0 0 0 .5-.5v-11a.5.5 0 0 0-.5-.5H15a2 2 0 0 0-2 2v10.569Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 608 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="M15.379 2.707a3 3 0 0 1 4.242 0l1.672 1.672a3 3 0 0 1 0 4.242l-12.5 12.5A3 3 0 0 1 6.672 22H3a1 1 0 0 1-1-1v-3.672a3 3 0 0 1 .879-2.121l12.5-12.5Zm2.828 1.414a1 1 0 0 0-1.414 0l-12.5 12.5a1 1 0 0 0-.293.707V20h2.672a1 1 0 0 0 .707-.293l12.5-12.5.707.707-.707-.707a1 1 0 0 0 0-1.414L18.207 4.12ZM13 21a1 1 0 0 1 1-1h7a1 1 0 0 1 0 2h-7a1 1 0 0 1-1-1Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 491 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="M8 5a2 2 0 1 0 0 4 2 2 0 0 0 0-4ZM4 7a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm13-1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm-3.5 1.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0Zm7.301 9.7c-.836-2.6-2.88-3.503-4.575-3.111a1 1 0 0 1-.451-1.949c2.815-.651 5.81.966 6.93 4.448a2.49 2.49 0 0 1-.506 2.43A2.92 2.92 0 0 1 20 20h-2a1 1 0 1 1 0-2h2a.92.92 0 0 0 .69-.295.49.49 0 0 0 .112-.505ZM8 14c-1.865 0-3.878 1.274-4.681 4.151a.57.57 0 0 0 .132.55c.15.171.4.299.695.299h7.708a.93.93 0 0 0 .695-.299.57.57 0 0 0 .132-.55C11.878 15.274 9.865 14 8 14Zm0-2c2.87 0 5.594 1.98 6.607 5.613.53 1.9-1.09 3.387-2.753 3.387H4.146c-1.663 0-3.283-1.487-2.753-3.387C2.406 13.981 5.129 12 8 12Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 795 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.5 4a.5.5 0 0 0-.5.5V10a1 1 0 1 1-2 0V5.5a.5.5 0 0 0-1 0V11a1 1 0 1 1-2 0V7.5a.5.5 0 0 0-1 0v6a6.5 6.5 0 1 0 13 0V9a1.996 1.996 0 0 0-2 2v.838c0 .826-.529 1.559-1.312 1.82A2.47 2.47 0 0 0 14 16a1 1 0 1 1-2 0 4.47 4.47 0 0 1 3-4.22V11c0-1.014.379-1.941 1-2.646V5.5a.5.5 0 0 0-1 0V10a1 1 0 1 1-2 0V4.5a.5.5 0 0 0-.5-.5Zm2.112-.838A2.5 2.5 0 0 1 18 5.5v1.626q.481-.124 1-.126a2 2 0 0 1 2 2v4.5a8.5 8.5 0 0 1-17 0v-6a2.5 2.5 0 0 1 3.039-2.442 2.5 2.5 0 0 1 3.349-1.896 2.498 2.498 0 0 1 4.224 0Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 637 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="M14.5 4a5.5 5.5 0 0 0-5.078 7.616 1 1 0 0 1-.216 1.092L4.37 17.543a1 1 0 0 0 0 1.414l.672.672a1 1 0 0 0 1.414 0l4.835-4.835a1 1 0 0 1 1.092-.216A5.5 5.5 0 0 0 20 9.414l-1.293 1.293a3.828 3.828 0 1 1-5.414-5.414L14.585 4H14.5ZM7 9.5a7.5 7.5 0 0 1 9.969-7.084 1 1 0 0 1 .378 1.651l-2.64 2.64a1.829 1.829 0 0 0 2.586 2.586l2.64-2.64a1 1 0 0 1 1.65.378 7.5 7.5 0 0 1-9.328 9.627l-4.384 4.385a3 3 0 0 1-4.242 0l-.672-.672a3 3 0 0 1 0-4.242l4.385-4.385A7.5 7.5 0 0 1 7 9.5Z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 610 B |
@@ -17,12 +17,7 @@ module.exports = function (api) {
|
||||
],
|
||||
plugins: [
|
||||
'macros',
|
||||
[
|
||||
'babel-plugin-react-compiler',
|
||||
{
|
||||
runtimeModule: 'react-compiler-runtime',
|
||||
},
|
||||
],
|
||||
['babel-plugin-react-compiler', {target: '18'}],
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
|
||||
@@ -23,7 +23,9 @@ func ExpandPostText(post *appbsky.FeedPost) string {
|
||||
}
|
||||
|
||||
// bail out if bounds checks fail
|
||||
if int(facet.Index.ByteStart)+charsAdded > len(postText) || int(facet.Index.ByteEnd)+charsAdded > len(postText) {
|
||||
if facet.Index.ByteStart > facet.Index.ByteEnd ||
|
||||
int(facet.Index.ByteStart)+charsAdded > len(postText) ||
|
||||
int(facet.Index.ByteEnd)+charsAdded > len(postText) {
|
||||
return false
|
||||
}
|
||||
linkText := postText[int(facet.Index.ByteStart)+charsAdded : int(facet.Index.ByteEnd)+charsAdded]
|
||||
|
||||
@@ -38,6 +38,8 @@ type Server struct {
|
||||
httpd *http.Server
|
||||
xrpcc *xrpc.Client
|
||||
cfg *Config
|
||||
|
||||
ipccClient http.Client
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -105,6 +107,13 @@ func serve(cctx *cli.Context) error {
|
||||
ipccHost: ipccHost,
|
||||
staticCDNHost: staticCDNHost,
|
||||
},
|
||||
ipccClient: http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create the HTTP server.
|
||||
@@ -244,6 +253,10 @@ func serve(cctx *cli.Context) error {
|
||||
e.GET("/settings/external-embeds", server.WebGeneric)
|
||||
e.GET("/settings/accessibility", server.WebGeneric)
|
||||
e.GET("/settings/appearance", server.WebGeneric)
|
||||
e.GET("/settings/account", server.WebGeneric)
|
||||
e.GET("/settings/privacy-and-security", server.WebGeneric)
|
||||
e.GET("/settings/content-and-media", server.WebGeneric)
|
||||
e.GET("/settings/about", server.WebGeneric)
|
||||
e.GET("/sys/debug", server.WebGeneric)
|
||||
e.GET("/sys/debug-mod", server.WebGeneric)
|
||||
e.GET("/sys/log", server.WebGeneric)
|
||||
@@ -584,15 +597,8 @@ func (srv *Server) WebIpCC(c echo.Context) error {
|
||||
}
|
||||
ipccUrlBuilder.Path = "ipccdata.IpCcService/Lookup"
|
||||
ipccUrl := ipccUrlBuilder.String()
|
||||
cl := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
postBodyReader := bytes.NewReader(request)
|
||||
response, err := cl.Post(ipccUrl, "application/json", postBodyReader)
|
||||
response, err := srv.ipccClient.Post(ipccUrl, "application/json", postBodyReader)
|
||||
if err != nil {
|
||||
log.Warnf("ipcc backend error %s", err)
|
||||
return c.JSON(500, IPCCResponse{})
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
#splash {
|
||||
position: fixed;
|
||||
@@ -75,6 +76,10 @@
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%) translateY(-50px);
|
||||
}
|
||||
/* We need this style to prevent web dropdowns from shifting the display when opening */
|
||||
body {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% include "scripts.html" %}
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
<meta name="article:published_time" content="{{ postView.IndexedAt }}">
|
||||
<meta name="article:published_time" content="{{ postView.IndexedAt }}">
|
||||
<link rel="alternate" type="application/json+oembed" href="https://embed.bsky.app/oembed?format=json&url={{ postView.Uri | urlencode }}" />
|
||||
<link rel="alternate" href="{{ postView.Uri }}" />
|
||||
{% endif -%}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
{%- if requestHost %}
|
||||
<link rel="alternate" type="application/rss+xml" href="https://{{ requestHost }}/profile/{{ profileView.Did }}/rss">
|
||||
{% endif %}
|
||||
<link rel="alternate" href="at://{{ profileView.Did }}/app.bsky.actor.profile/self" />
|
||||
{% endif -%}
|
||||
{%- endblock %}
|
||||
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
# Build instructions
|
||||
|
||||
## App Build
|
||||
## Running Web App
|
||||
|
||||
- `yarn`
|
||||
- `yarn web`
|
||||
|
||||
You're all set!
|
||||
|
||||
## iOS/Android Build
|
||||
|
||||
### Native Environment Setup
|
||||
|
||||
This is NOT required when developing for web.
|
||||
|
||||
- Set up your environment [using the expo instructions](https://docs.expo.dev/guides/local-app-development/).
|
||||
- make sure that the JAVA_HOME points to the zulu-17 directory in your `.zshrc` or `.bashrc` file: `export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home`. DO NOT use another JDK or you will encounter build errors.
|
||||
@@ -22,7 +33,68 @@
|
||||
- After initial setup:
|
||||
- Copy `google-services.json.example` to `google-services.json` or provide your own `google-services.json`. (A real firebase project is NOT required)
|
||||
- `npx expo prebuild` -> you will also need to run this anytime `app.json` or native `package.json` deps change
|
||||
- `yarn intl:build` -> you will also need to run this anytime `./src/locale/{locale}/messages.po` change
|
||||
|
||||
### Running the Native App
|
||||
|
||||
- iOS: `yarn ios`
|
||||
- Xcode must be installed for this to run.
|
||||
- A simulator must be preconfigured in Xcode settings.
|
||||
- if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`.
|
||||
- if the simulator download keeps failing you can download it from the developer website.
|
||||
- [Apple Developer](https://developer.apple.com/download/all/?q=Simulator%20Runtime)
|
||||
- `xcode-select -s /Applications/Xcode.app`
|
||||
- `xcodebuild -runFirstLaunch`
|
||||
- `xcrun simctl runtime add "~/Downloads/iOS_17.4_Simulator_Runtime.dmg"` (adapt the path to the downloaded file)
|
||||
- In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`.
|
||||
- Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator.
|
||||
- Make sure you do have a certificate: open Xcode > Settings > Accounts > (sign-in) > Manage Certificates > + > Apple Development > Done.
|
||||
- If you still encounter issues, try `rm -rf ios` before trying to build again (`yarn ios`)
|
||||
- Android: `yarn android`
|
||||
- Install "Android Studio"
|
||||
- Make sure you have the Android SDK installed (Android Studio > Tools > Android SDK).
|
||||
- In "SDK Platforms": "Android x" (where x is Android's current version).
|
||||
- In "SDK Tools": "Android SDK Build-Tools" and "Android Emulator" are required.
|
||||
- Add `export ANDROID_HOME=/Users/<your_username>/Library/Android/sdk` to your `.zshrc` or `.bashrc` (and restart your terminal).
|
||||
- Setup an emulator (Android Studio > Tools > Device Manager).
|
||||
- Web: `yarn web` (see the top of this file).
|
||||
|
||||
After you do `yarn ios` and `yarn android` once, you can later just run `yarn web` and then press either `i` or `a` to open iOS and Android emulators respectively which is much faster. However, if you make native changes, you'll have to do `yarn prebuild -p ios` and `yarn prebuild -p android` and then `yarn ios` and `yarn android` again before you can continue with the same workflow.
|
||||
|
||||
### Tips
|
||||
|
||||
- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
|
||||
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release`.
|
||||
- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
|
||||
- `npx react-native info` Checks what has been installed.
|
||||
- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
|
||||
- The Android simulator won't be able to access localhost services unless you run `adb reverse tcp:{PORT} tcp:{PORT}`
|
||||
- For instance, the locally-hosted dev-wallet will need `adb reverse tcp:3001 tcp:3001`
|
||||
- For some reason, the typescript compiler chokes on platform-specific files (e.g. `foo.native.ts`) but only when compiling for Web thus far. Therefore we always have one version of the file that doesn't use a platform specifier, and that should be the Web version. ([More info](https://stackoverflow.com/questions/44001050/platform-specific-import-component-in-react-native-with-typescript).)
|
||||
|
||||
### Running E2E Tests
|
||||
|
||||
- Start in various console tabs:
|
||||
- `yarn e2e:mock-server`
|
||||
- `yarn e2e:metro`
|
||||
- Run once: `yarn e2e:build`
|
||||
- Each test run: `yarn e2e:run`
|
||||
|
||||
### Adding Sentry
|
||||
|
||||
Adding Sentry is NOT required. You can keep `SENTRY_AUTH_TOKEN=` in `.env` which will build the app without Sentry.
|
||||
|
||||
However, if you're a part of the Bluesky team and want to enable Sentry, fill in `SENTRY_AUTH_TOKEN` in your `.env`. It can be created on the Sentry dashboard using [these instructions](https://docs.expo.dev/guides/using-sentry/#sign-up-for-a-sentry-account-and-create-a-project).
|
||||
|
||||
If you change `SENTRY_AUTH_TOKEN`, you need to do `yarn prebuild` before running `yarn ios` or `yarn android` again.
|
||||
|
||||
### Adding and Updating Locales
|
||||
|
||||
- `yarn intl:build` -> you will also need to run this anytime `./src/locale/{locale}/messages.po` change
|
||||
|
||||
## Running the Backend Locally
|
||||
|
||||
This is NOT required for app development but if you also want to develop the Bluesky *backend* locally too, you'll need this.
|
||||
|
||||
- Start the dev servers
|
||||
- `git clone git@github.com:bluesky-social/atproto.git`
|
||||
- `cd atproto`
|
||||
@@ -33,55 +105,13 @@
|
||||
- Start the docker daemon (on MacOS this entails starting the Docker Desktop app)
|
||||
- Launch a Postgres database on port 5432
|
||||
- `cd packages/dev-env && pnpm start`
|
||||
- Run the dev app
|
||||
- iOS: `yarn ios`
|
||||
- Xcode must be installed for this to run.
|
||||
- A simulator must be preconfigured in Xcode settings.
|
||||
- if no iOS versions are available, install the iOS runtime at `Xcode > Settings > Platforms`.
|
||||
- if the simulator download keeps failing you can download it from the developer website.
|
||||
- [Apple Developer](https://developer.apple.com/download/all/?q=Simulator%20Runtime)
|
||||
- `xcode-select -s /Applications/Xcode.app`
|
||||
- `xcodebuild -runFirstLaunch`
|
||||
- `xcrun simctl runtime add "~/Downloads/iOS_17.4_Simulator_Runtime.dmg"` (adapt the path to the downloaded file)
|
||||
- In addition, ensure Xcode Command Line Tools are installed using `xcode-select --install`.
|
||||
- Expo will require you to configure Xcode Signing. Follow the linked instructions. Error messages in Xcode related to the signing process can be safely ignored when installing on the iOS Simulator; Expo merely requires the profile to exist in order to install the app on the Simulator.
|
||||
- Make sure you do have a certificate: open Xcode > Settings > Accounts > (sign-in) > Manage Certificates > + > Apple Development > Done.
|
||||
- If you still encounter issues, try `rm -rf ios` before trying to build again (`yarn ios`)
|
||||
- Android: `yarn android`
|
||||
- Install "Android Studio"
|
||||
- Make sure you have the Android SDK installed (Android Studio > Tools > Android SDK).
|
||||
- In "SDK Platforms": "Android x" (where x is Android's current version).
|
||||
- In "SDK Tools": "Android SDK Build-Tools" and "Android Emulator" are required.
|
||||
- Add `export ANDROID_HOME=/Users/<your_username>/Library/Android/sdk` to your `.zshrc` or `.bashrc` (and restart your terminal).
|
||||
- Setup an emulator (Android Studio > Tools > Device Manager).
|
||||
- Web: `yarn web`
|
||||
- If you are cloning or forking this repo as an open-source developer, please check the tips below as well
|
||||
- Run e2e tests
|
||||
- Start in various console tabs:
|
||||
- `yarn e2e:mock-server`
|
||||
- `yarn e2e:metro`
|
||||
- Run once: `yarn e2e:build`
|
||||
- Each test run: `yarn e2e:run`
|
||||
- Tips
|
||||
- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
|
||||
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release`.
|
||||
- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
|
||||
- `npx react-native info` Checks what has been installed.
|
||||
- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
|
||||
- The Android simulator won't be able to access localhost services unless you run `adb reverse tcp:{PORT} tcp:{PORT}`
|
||||
- For instance, the locally-hosted dev-wallet will need `adb reverse tcp:3001 tcp:3001`
|
||||
- For some reason, the typescript compiler chokes on platform-specific files (e.g. `foo.native.ts`) but only when compiling for Web thus far. Therefore we always have one version of the file that doesn't use a platform specifier, and that should be the Web version. ([More info](https://stackoverflow.com/questions/44001050/platform-specific-import-component-in-react-native-with-typescript).)
|
||||
|
||||
### Adding Sentry
|
||||
|
||||
Adding Sentry is NOT required. You can keep `SENTRY_AUTH_TOKEN=` in `.env` which will build the app without Sentry.
|
||||
|
||||
However, if you're a part of the Bluesky team and want to enable Sentry, fill in `SENTRY_AUTH_TOKEN` in your `.env`. It can be created on the Sentry dashboard using [these instructions](https://docs.expo.dev/guides/using-sentry/#sign-up-for-a-sentry-account-and-create-a-project).
|
||||
|
||||
If you change `SENTRY_AUTH_TOKEN`, you need to do `yarn prebuild` before running `yarn ios` or `yarn android` again.
|
||||
Then, when logging in or creating an account, point it to the localhost port of the devserver.
|
||||
|
||||
## Go-Server Build
|
||||
|
||||
The Go server in this repository is only used for serving the web app in production. Usually you won't need to touch it.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Go](https://go.dev/)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const React = require('react')
|
||||
const $empty = Symbol.for('react.memo_cache_sentinel')
|
||||
/**
|
||||
* DANGER: this hook is NEVER meant to be called directly!
|
||||
*
|
||||
* Note that this is a temporary userspace implementation of this function
|
||||
* from React 19. It is not as efficient and may invalidate more frequently
|
||||
* than the official API. Please upgrade to React 19 as soon as you can.
|
||||
**/
|
||||
export function c(size) {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
return React.useState(() => {
|
||||
const $ = new Array(size)
|
||||
for (let ii = 0; ii < size; ii++) {
|
||||
$[ii] = $empty
|
||||
}
|
||||
// @ts-ignore
|
||||
$[$empty] = true
|
||||
return $
|
||||
})[0]
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"name": "react-compiler-runtime",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0"
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,20 @@ module.exports = {
|
||||
'en',
|
||||
'ca',
|
||||
'de',
|
||||
'en-GB',
|
||||
'es',
|
||||
'fi',
|
||||
'fr',
|
||||
'ga',
|
||||
'hi',
|
||||
'hu',
|
||||
'id',
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'pt-BR',
|
||||
'ru',
|
||||
'th',
|
||||
'tr',
|
||||
'uk',
|
||||
'zh-CN',
|
||||
|
||||
@@ -6,6 +6,32 @@ cfg.resolver.sourceExts = process.env.RN_SRC_EXT
|
||||
? process.env.RN_SRC_EXT.split(',').concat(cfg.resolver.sourceExts)
|
||||
: cfg.resolver.sourceExts
|
||||
|
||||
if (cfg.resolver.resolveRequest) {
|
||||
throw Error('Update this override because it is conflicting now.')
|
||||
}
|
||||
cfg.resolver.resolveRequest = (context, moduleName, platform) => {
|
||||
// HACK: manually resolve a few packages that use `exports` in `package.json`.
|
||||
// A proper solution is to enable `unstable_enablePackageExports` but this needs careful testing.
|
||||
if (moduleName.startsWith('multiformats/hashes/hasher')) {
|
||||
return context.resolveRequest(
|
||||
context,
|
||||
'multiformats/dist/src/hashes/hasher',
|
||||
platform,
|
||||
)
|
||||
}
|
||||
if (moduleName.startsWith('multiformats/cid')) {
|
||||
return context.resolveRequest(
|
||||
context,
|
||||
'multiformats/dist/src/cid',
|
||||
platform,
|
||||
)
|
||||
}
|
||||
if (moduleName === '@ipld/dag-cbor') {
|
||||
return context.resolveRequest(context, '@ipld/dag-cbor/src', platform)
|
||||
}
|
||||
return context.resolveRequest(context, moduleName, platform)
|
||||
}
|
||||
|
||||
cfg.transformer.getTransformOptions = async () => ({
|
||||
transform: {
|
||||
experimentalImportSupport: true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package expo.modules.bottomsheet
|
||||
|
||||
import android.content.Context
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewStructure
|
||||
@@ -17,7 +18,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import expo.modules.kotlin.AppContext
|
||||
import expo.modules.kotlin.viewevent.EventDispatcher
|
||||
import expo.modules.kotlin.views.ExpoView
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
class BottomSheetView(
|
||||
context: Context,
|
||||
@@ -58,17 +59,18 @@ class BottomSheetView(
|
||||
if (value < 0) {
|
||||
0f
|
||||
} else {
|
||||
value
|
||||
dpToPx(value)
|
||||
}
|
||||
}
|
||||
|
||||
var maxHeight = this.screenHeight
|
||||
set(value) {
|
||||
val px = dpToPx(value)
|
||||
field =
|
||||
if (value > this.screenHeight) {
|
||||
this.screenHeight.toFloat()
|
||||
if (px > this.screenHeight) {
|
||||
this.screenHeight
|
||||
} else {
|
||||
value
|
||||
px
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,67 +157,63 @@ class BottomSheetView(
|
||||
if (this.isOpen || this.isOpening || this.isClosing) return
|
||||
|
||||
val contentHeight = this.getContentHeight()
|
||||
|
||||
val dialog = BottomSheetDialog(context)
|
||||
dialog.setContentView(dialogRootViewGroup)
|
||||
dialog.setCancelable(!preventDismiss)
|
||||
dialog.setOnShowListener {
|
||||
val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
bottomSheet?.let {
|
||||
// Let the outside view handle the background color on its own, the default for this is
|
||||
// white and we don't want that.
|
||||
it.setBackgroundColor(0)
|
||||
|
||||
val behavior = BottomSheetBehavior.from(it)
|
||||
|
||||
behavior.isFitToContents = true
|
||||
behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)
|
||||
if (contentHeight > this.screenHeight) {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
this.selectedSnapPoint = 2
|
||||
} else {
|
||||
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||
this.selectedSnapPoint = 1
|
||||
}
|
||||
behavior.skipCollapsed = true
|
||||
behavior.isDraggable = true
|
||||
behavior.isHideable = true
|
||||
|
||||
behavior.addBottomSheetCallback(
|
||||
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onStateChanged(
|
||||
bottomSheet: View,
|
||||
newState: Int,
|
||||
) {
|
||||
when (newState) {
|
||||
BottomSheetBehavior.STATE_EXPANDED -> {
|
||||
selectedSnapPoint = 2
|
||||
}
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> {
|
||||
selectedSnapPoint = 1
|
||||
}
|
||||
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
|
||||
selectedSnapPoint = 1
|
||||
}
|
||||
BottomSheetBehavior.STATE_HIDDEN -> {
|
||||
selectedSnapPoint = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(
|
||||
bottomSheet: View,
|
||||
slideOffset: Float,
|
||||
) { }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
this.isClosing = true
|
||||
this.destroy()
|
||||
}
|
||||
|
||||
val bottomSheet = dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
bottomSheet?.let {
|
||||
it.setBackgroundColor(0)
|
||||
|
||||
val behavior = BottomSheetBehavior.from(it)
|
||||
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
behavior.isFitToContents = true
|
||||
behavior.halfExpandedRatio = this.clampRatio(this.getTargetHeight() / this.screenHeight)
|
||||
behavior.skipCollapsed = true
|
||||
behavior.isDraggable = true
|
||||
behavior.isHideable = true
|
||||
|
||||
if (contentHeight >= this.screenHeight || this.minHeight >= this.screenHeight) {
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
this.selectedSnapPoint = 2
|
||||
} else {
|
||||
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
|
||||
this.selectedSnapPoint = 1
|
||||
}
|
||||
|
||||
behavior.addBottomSheetCallback(
|
||||
object : BottomSheetBehavior.BottomSheetCallback() {
|
||||
override fun onStateChanged(
|
||||
bottomSheet: View,
|
||||
newState: Int,
|
||||
) {
|
||||
when (newState) {
|
||||
BottomSheetBehavior.STATE_EXPANDED -> {
|
||||
selectedSnapPoint = 2
|
||||
}
|
||||
BottomSheetBehavior.STATE_COLLAPSED -> {
|
||||
selectedSnapPoint = 1
|
||||
}
|
||||
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
|
||||
selectedSnapPoint = 1
|
||||
}
|
||||
BottomSheetBehavior.STATE_HIDDEN -> {
|
||||
selectedSnapPoint = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlide(
|
||||
bottomSheet: View,
|
||||
slideOffset: Float,
|
||||
) { }
|
||||
},
|
||||
)
|
||||
}
|
||||
this.isOpening = true
|
||||
dialog.show()
|
||||
this.dialog = dialog
|
||||
@@ -336,4 +334,11 @@ class BottomSheetView(
|
||||
override fun addChildrenForAccessibility(outChildren: ArrayList<View>?) { }
|
||||
|
||||
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent?): Boolean = false
|
||||
|
||||
// https://stackoverflow.com/questions/11862391/getheight-px-or-dpi
|
||||
fun dpToPx(dp: Float): Float {
|
||||
val displayMetrics = context.resources.displayMetrics
|
||||
val px = dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)
|
||||
return px
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,28 +27,38 @@ class SheetViewController: UIViewController {
|
||||
return
|
||||
}
|
||||
|
||||
if contentHeight > screenHeight - 100 {
|
||||
sheet.detents = [
|
||||
.large()
|
||||
]
|
||||
sheet.selectedDetentIdentifier = .large
|
||||
} else {
|
||||
if #available(iOS 16.0, *) {
|
||||
if #available(iOS 16.0, *) {
|
||||
if contentHeight > screenHeight - 100 {
|
||||
sheet.detents = [
|
||||
.large()
|
||||
]
|
||||
sheet.selectedDetentIdentifier = .large
|
||||
} else {
|
||||
sheet.detents = [
|
||||
.custom { _ in
|
||||
return contentHeight
|
||||
}
|
||||
]
|
||||
if !preventExpansion {
|
||||
sheet.detents.append(.large())
|
||||
}
|
||||
sheet.selectedDetentIdentifier = .medium
|
||||
}
|
||||
} else {
|
||||
if contentHeight > screenHeight / 2 {
|
||||
sheet.detents = [
|
||||
.large()
|
||||
]
|
||||
sheet.selectedDetentIdentifier = .large
|
||||
} else {
|
||||
sheet.detents = [
|
||||
.medium()
|
||||
]
|
||||
if !preventExpansion {
|
||||
sheet.detents.append(.large())
|
||||
}
|
||||
sheet.selectedDetentIdentifier = .medium
|
||||
}
|
||||
|
||||
if !preventExpansion {
|
||||
sheet.detents.append(.large())
|
||||
}
|
||||
sheet.selectedDetentIdentifier = .medium
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,13 @@ const NativeView: React.ComponentType<
|
||||
|
||||
const NativeModule = requireNativeModule('BottomSheet')
|
||||
|
||||
const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16
|
||||
|
||||
export class BottomSheetNativeComponent extends React.Component<
|
||||
BottomSheetViewProps,
|
||||
{
|
||||
open: boolean
|
||||
viewHeight?: number
|
||||
}
|
||||
> {
|
||||
ref = React.createRef<any>()
|
||||
@@ -67,6 +70,19 @@ export class BottomSheetNativeComponent extends React.Component<
|
||||
const {children, backgroundColor, ...rest} = this.props
|
||||
const cornerRadius = rest.cornerRadius ?? 0
|
||||
|
||||
let extraStyles
|
||||
if (isIOS15 && this.state.viewHeight) {
|
||||
const {viewHeight} = this.state
|
||||
if (viewHeight < screenHeight / 2) {
|
||||
extraStyles = {
|
||||
height: viewHeight,
|
||||
marginTop: screenHeight / 2 - viewHeight,
|
||||
borderTopLeftRadius: cornerRadius,
|
||||
borderTopRightRadius: cornerRadius,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.state.open) {
|
||||
return null
|
||||
}
|
||||
@@ -92,8 +108,14 @@ export class BottomSheetNativeComponent extends React.Component<
|
||||
borderTopLeftRadius: cornerRadius,
|
||||
borderTopRightRadius: cornerRadius,
|
||||
},
|
||||
extraStyles,
|
||||
]}>
|
||||
<View onLayout={this.updateLayout}>
|
||||
<View
|
||||
onLayout={e => {
|
||||
const {height} = e.nativeEvent.layout
|
||||
this.setState({viewHeight: height})
|
||||
this.updateLayout()
|
||||
}}>
|
||||
<BottomSheetPortalProvider>{children}</BottomSheetPortalProvider>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bsky.app",
|
||||
"version": "1.93.0",
|
||||
"version": "1.94.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
@@ -68,7 +68,8 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
||||
"@haileyok/bluesky-video": "0.2.2",
|
||||
"@haileyok/bluesky-video": "0.2.4",
|
||||
"@ipld/dag-cbor": "^9.2.0",
|
||||
"@lingui/react": "^4.5.0",
|
||||
"@mattermost/react-native-paste-input": "^0.7.1",
|
||||
"@miblanchard/react-native-slider": "^2.3.1",
|
||||
@@ -88,18 +89,18 @@
|
||||
"@tanstack/query-async-storage-persister": "^5.25.0",
|
||||
"@tanstack/react-query": "^5.8.1",
|
||||
"@tanstack/react-query-persist-client": "^5.25.0",
|
||||
"@tiptap/core": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-document": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-hard-break": "^2.0.3",
|
||||
"@tiptap/extension-history": "^2.0.3",
|
||||
"@tiptap/extension-mention": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-paragraph": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-placeholder": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-text": "^2.0.0-beta.220",
|
||||
"@tiptap/html": "^2.1.11",
|
||||
"@tiptap/pm": "^2.0.0-beta.220",
|
||||
"@tiptap/react": "^2.0.0-beta.220",
|
||||
"@tiptap/suggestion": "^2.0.0-beta.220",
|
||||
"@tiptap/core": "^2.6.6",
|
||||
"@tiptap/extension-document": "^2.6.6",
|
||||
"@tiptap/extension-hard-break": "^2.6.6",
|
||||
"@tiptap/extension-history": "^2.6.6",
|
||||
"@tiptap/extension-mention": "^2.6.6",
|
||||
"@tiptap/extension-paragraph": "^2.6.6",
|
||||
"@tiptap/extension-placeholder": "^2.6.6",
|
||||
"@tiptap/extension-text": "^2.6.6",
|
||||
"@tiptap/html": "^2.6.6",
|
||||
"@tiptap/pm": "^2.6.6",
|
||||
"@tiptap/react": "^2.6.6",
|
||||
"@tiptap/suggestion": "^2.6.6",
|
||||
"@types/invariant": "^2.2.37",
|
||||
"@types/lodash.throttle": "^4.1.9",
|
||||
"@types/node": "^18.16.2",
|
||||
@@ -158,13 +159,14 @@
|
||||
"lodash.set": "^4.3.2",
|
||||
"lodash.shuffle": "^4.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"multiformats": "^13.1.0",
|
||||
"nanoid": "^5.0.5",
|
||||
"normalize-url": "^8.0.0",
|
||||
"patch-package": "^6.5.1",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"psl": "^1.9.0",
|
||||
"react": "18.2.0",
|
||||
"react-compiler-runtime": "file:./lib/react-compiler-runtime",
|
||||
"react-compiler-runtime": "^19.0.0-beta-6fc168f-20241025",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-image-crop": "^11.0.7",
|
||||
"react-keyed-flatten-children": "^3.0.0",
|
||||
@@ -241,7 +243,7 @@
|
||||
"babel-loader": "^9.1.2",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"babel-plugin-module-resolver": "^5.0.0",
|
||||
"babel-plugin-react-compiler": "^0.0.0-experimental-592953e-20240517",
|
||||
"babel-plugin-react-compiler": "^19.0.0-beta-6fc168f-20241025",
|
||||
"babel-plugin-react-native-web": "^0.18.12",
|
||||
"babel-preset-expo": "^10.0.0",
|
||||
"eslint": "^8.19.0",
|
||||
@@ -249,7 +251,7 @@
|
||||
"eslint-plugin-ft-flow": "^2.0.3",
|
||||
"eslint-plugin-lingui": "^0.2.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-compiler": "^0.0.0-experimental-c8b3f72-20240517",
|
||||
"eslint-plugin-react-compiler": "^19.0.0-beta-6fc168f-20241025",
|
||||
"eslint-plugin-react-native-a11y": "^3.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^12.0.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
@@ -280,7 +282,8 @@
|
||||
"**/zod": "3.23.8",
|
||||
"**/expo-constants": "16.0.1",
|
||||
"**/expo-device": "6.0.2",
|
||||
"@react-native/babel-preset": "0.74.1"
|
||||
"@react-native/babel-preset": "0.74.1",
|
||||
"@radix-ui/react-focus-scope": "1.1.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo/ios",
|
||||
|
||||
@@ -14,7 +14,6 @@ import * as SplashScreen from 'expo-splash-screen'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||
import {QueryProvider} from '#/lib/react-query'
|
||||
import {
|
||||
initialize,
|
||||
@@ -85,7 +84,6 @@ function InnerApp() {
|
||||
const theme = useColorModeTheme()
|
||||
const {_} = useLingui()
|
||||
|
||||
useIntentHandler()
|
||||
const hasCheckedReferrer = useStarterPackEntry()
|
||||
|
||||
// init
|
||||
|
||||
@@ -9,7 +9,6 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useIntentHandler} from '#/lib/hooks/useIntentHandler'
|
||||
import {QueryProvider} from '#/lib/react-query'
|
||||
import {Provider as StatsigProvider} from '#/lib/statsig/statsig'
|
||||
import {ThemeProvider} from '#/lib/ThemeContext'
|
||||
@@ -42,7 +41,6 @@ import {
|
||||
import {readLastActiveAccount} from '#/state/session/util'
|
||||
import {Provider as ShellStateProvider} from '#/state/shell'
|
||||
import {Provider as ComposerProvider} from '#/state/shell/composer'
|
||||
import {useComposerKeyboardShortcut} from '#/state/shell/composer/useComposerKeyboardShortcut'
|
||||
import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out'
|
||||
import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide'
|
||||
import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||
@@ -72,11 +70,8 @@ function InnerApp() {
|
||||
const {resumeSession} = useSessionApi()
|
||||
const theme = useColorModeTheme()
|
||||
const {_} = useLingui()
|
||||
useIntentHandler()
|
||||
const hasCheckedReferrer = useStarterPackEntry()
|
||||
|
||||
useComposerKeyboardShortcut()
|
||||
|
||||
// init
|
||||
useEffect(() => {
|
||||
async function onLaunch(account?: SessionAccount) {
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
|
||||
import {timeout} from '#/lib/async/timeout'
|
||||
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
|
||||
import {usePalette} from '#/lib/hooks/usePalette'
|
||||
import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration'
|
||||
import {buildStateObject} from '#/lib/routes/helpers'
|
||||
import {
|
||||
@@ -56,7 +55,6 @@ import {ModerationModlistsScreen} from '#/view/screens/ModerationModlists'
|
||||
import {ModerationMutedAccounts} from '#/view/screens/ModerationMutedAccounts'
|
||||
import {NotFoundScreen} from '#/view/screens/NotFound'
|
||||
import {NotificationsScreen} from '#/view/screens/Notifications'
|
||||
import {NotificationsSettingsScreen} from '#/view/screens/NotificationsSettings'
|
||||
import {PostThreadScreen} from '#/view/screens/PostThread'
|
||||
import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds'
|
||||
import {PreferencesFollowingFeed} from '#/view/screens/PreferencesFollowingFeed'
|
||||
@@ -88,13 +86,19 @@ import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'
|
||||
import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'
|
||||
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
|
||||
import {AppearanceSettingsScreen} from '#/screens/Settings/AppearanceSettings'
|
||||
import {NotificationSettingsScreen} from '#/screens/Settings/NotificationSettings'
|
||||
import {
|
||||
StarterPackScreen,
|
||||
StarterPackScreenShort,
|
||||
} from '#/screens/StarterPack/StarterPackScreen'
|
||||
import {Wizard} from '#/screens/StarterPack/Wizard'
|
||||
import {useTheme} from '#/alf'
|
||||
import {router} from '#/routes'
|
||||
import {Referrer} from '../modules/expo-bluesky-swiss-army'
|
||||
import {AboutSettingsScreen} from './screens/Settings/AboutSettings'
|
||||
import {AccountSettingsScreen} from './screens/Settings/AccountSettings'
|
||||
import {ContentAndMediaSettingsScreen} from './screens/Settings/ContentAndMediaSettings'
|
||||
import {PrivacyAndSecuritySettingsScreen} from './screens/Settings/PrivacyAndSecuritySettings'
|
||||
|
||||
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
|
||||
|
||||
@@ -322,7 +326,39 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
||||
name="AppearanceSettings"
|
||||
getComponent={() => AppearanceSettingsScreen}
|
||||
options={{
|
||||
title: title(msg`Appearance Settings`),
|
||||
title: title(msg`Appearance`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="AccountSettings"
|
||||
getComponent={() => AccountSettingsScreen}
|
||||
options={{
|
||||
title: title(msg`Account`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="PrivacyAndSecuritySettings"
|
||||
getComponent={() => PrivacyAndSecuritySettingsScreen}
|
||||
options={{
|
||||
title: title(msg`Privacy and Security`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="ContentAndMediaSettings"
|
||||
getComponent={() => ContentAndMediaSettingsScreen}
|
||||
options={{
|
||||
title: title(msg`Content and Media`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="AboutSettings"
|
||||
getComponent={() => AboutSettingsScreen}
|
||||
options={{
|
||||
title: title(msg`About`),
|
||||
requireAuth: true,
|
||||
}}
|
||||
/>
|
||||
@@ -342,8 +378,8 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
||||
options={{title: title(msg`Chat settings`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="NotificationsSettings"
|
||||
getComponent={() => NotificationsSettingsScreen}
|
||||
name="NotificationSettings"
|
||||
getComponent={() => NotificationSettingsScreen}
|
||||
options={{title: title(msg`Notification settings`), requireAuth: true}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
@@ -412,7 +448,7 @@ function TabsNavigator() {
|
||||
}
|
||||
|
||||
function HomeTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<HomeTab.Navigator
|
||||
@@ -422,7 +458,7 @@ function HomeTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
|
||||
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
|
||||
@@ -432,7 +468,7 @@ function HomeTabNavigator() {
|
||||
}
|
||||
|
||||
function SearchTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<SearchTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -441,7 +477,7 @@ function SearchTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
|
||||
{commonScreens(SearchTab as typeof HomeTab)}
|
||||
@@ -450,7 +486,7 @@ function SearchTabNavigator() {
|
||||
}
|
||||
|
||||
function NotificationsTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<NotificationsTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -459,7 +495,7 @@ function NotificationsTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<NotificationsTab.Screen
|
||||
name="Notifications"
|
||||
@@ -472,7 +508,7 @@ function NotificationsTabNavigator() {
|
||||
}
|
||||
|
||||
function MyProfileTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MyProfileTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -481,7 +517,7 @@ function MyProfileTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<MyProfileTab.Screen
|
||||
// @ts-ignore // TODO: fix this broken type in ProfileScreen
|
||||
@@ -498,7 +534,7 @@ function MyProfileTabNavigator() {
|
||||
}
|
||||
|
||||
function MessagesTabNavigator() {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
return (
|
||||
<MessagesTab.Navigator
|
||||
screenOptions={{
|
||||
@@ -507,7 +543,7 @@ function MessagesTabNavigator() {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<MessagesTab.Screen
|
||||
name="Messages"
|
||||
@@ -527,7 +563,7 @@ function MessagesTabNavigator() {
|
||||
* in a single ("flat") stack.
|
||||
*/
|
||||
const FlatNavigator = () => {
|
||||
const pal = usePalette('default')
|
||||
const t = useTheme()
|
||||
const numUnread = useUnreadNotifications()
|
||||
const screenListeners = useWebScrollRestoration()
|
||||
const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), numUnread)
|
||||
@@ -541,7 +577,7 @@ const FlatNavigator = () => {
|
||||
gestureEnabled: true,
|
||||
fullScreenGestureEnabled: true,
|
||||
headerShown: false,
|
||||
contentStyle: pal.view,
|
||||
contentStyle: t.atoms.bg,
|
||||
}}>
|
||||
<Flat.Screen
|
||||
name="Home"
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import React, {useCallback, useEffect} from 'react'
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Image as RNImage,
|
||||
AccessibilityInfo,
|
||||
Image as RNImage,
|
||||
StyleSheet,
|
||||
useColorScheme,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import {Image} from 'expo-image'
|
||||
import Animated, {
|
||||
Easing,
|
||||
interpolate,
|
||||
runOnJS,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
Easing,
|
||||
} from 'react-native-reanimated'
|
||||
import MaskedView from '@react-native-masked-view/masked-view'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import Svg, {Path, SvgProps} from 'react-native-svg'
|
||||
import {Image} from 'expo-image'
|
||||
import * as SplashScreen from 'expo-splash-screen'
|
||||
import MaskedView from '@react-native-masked-view/masked-view'
|
||||
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {Logotype} from '#/view/icons/Logotype'
|
||||
|
||||
// @ts-ignore
|
||||
import splashImagePointer from '../assets/splash.png'
|
||||
// @ts-ignore
|
||||
|
||||
@@ -102,6 +102,9 @@ export const atoms = {
|
||||
/*
|
||||
* Flex
|
||||
*/
|
||||
gap_0: {
|
||||
gap: 0,
|
||||
},
|
||||
gap_2xs: {
|
||||
gap: tokens.space._2xs,
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {useFonts} from 'expo-font'
|
||||
import {TextStyle} from 'react-native'
|
||||
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {isAndroid, isWeb} from '#/platform/detection'
|
||||
import {Device, device} from '#/storage'
|
||||
|
||||
const FAMILIES = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif`
|
||||
const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"`
|
||||
|
||||
const factor = 0.0625 // 1 - (15/16)
|
||||
const fontScaleMultipliers: Record<Device['fontScale'], number> = {
|
||||
@@ -37,45 +37,65 @@ export function setFontFamily(fontFamily: Device['fontFamily']) {
|
||||
/*
|
||||
* Unused fonts are commented out, but the files are there if we need them.
|
||||
*/
|
||||
export function applyFonts(
|
||||
style: Record<string, any>,
|
||||
fontFamily: 'system' | 'theme',
|
||||
) {
|
||||
export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
if (fontFamily === 'theme') {
|
||||
style.fontFamily = 'InterVariable'
|
||||
if (isAndroid) {
|
||||
style.fontFamily =
|
||||
{
|
||||
400: 'Inter-Regular',
|
||||
500: 'Inter-Regular',
|
||||
600: 'Inter-SemiBold',
|
||||
700: 'Inter-SemiBold',
|
||||
800: 'Inter-ExtraBold',
|
||||
900: 'Inter-ExtraBold',
|
||||
}[String(style.fontWeight || '400')] || 'Inter-Regular'
|
||||
|
||||
if (style.fontStyle === 'italic') {
|
||||
style.fontFamily += 'Italic'
|
||||
if (style.fontStyle === 'italic') {
|
||||
if (style.fontFamily === 'Inter-Regular') {
|
||||
style.fontFamily = 'Inter-Italic'
|
||||
} else {
|
||||
style.fontFamily += 'Italic'
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* These are not supported on Android and actually break the styling.
|
||||
*/
|
||||
delete style.fontWeight
|
||||
delete style.fontStyle
|
||||
} else {
|
||||
style.fontFamily = 'InterVariable'
|
||||
|
||||
if (style.fontStyle === 'italic') {
|
||||
style.fontFamily += 'Italic'
|
||||
}
|
||||
}
|
||||
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily += `, ${FAMILIES}`
|
||||
// fallback families only supported on web
|
||||
style.fontFamily += `, ${WEB_FONT_FAMILIES}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable contextual alternates in Inter
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant}
|
||||
*/
|
||||
style.fontVariant = (style.fontVariant || []).concat('no-contextual')
|
||||
} else {
|
||||
// fallback families only supported on web
|
||||
if (isWeb) {
|
||||
style.fontFamily = style.fontFamily || FAMILIES
|
||||
style.fontFamily = style.fontFamily || WEB_FONT_FAMILIES
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridden to previous spacing for the `system` font option.
|
||||
* https://github.com/bluesky-social/social-app/commit/2419096e2409008b7d71fd6b8f8d0dd5b016e267
|
||||
*/
|
||||
style.letterSpacing = 0.25
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable contextual ligatures
|
||||
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/font-variant}
|
||||
*/
|
||||
style.fontVariant = (style.fontVariant || []).concat('no-contextual')
|
||||
}
|
||||
|
||||
/*
|
||||
* IMPORTANT: This is unused. Expo statically extracts these fonts.
|
||||
*
|
||||
* All used fonts MUST be configured here. Unused fonts can be commented out.
|
||||
*
|
||||
* This is used for both web fonts and native fonts.
|
||||
/**
|
||||
* Here only for bundling purposes, not actually used.
|
||||
*/
|
||||
export function DO_NOT_USE() {
|
||||
return useFonts({
|
||||
InterVariable: require('../../assets/fonts/inter/InterVariable.ttf'),
|
||||
'InterVariable-Italic': require('../../assets/fonts/inter/InterVariable-Italic.ttf'),
|
||||
})
|
||||
}
|
||||
export {DO_NOT_USE} from '#/alf/util/unusedUseFonts'
|
||||
|
||||
@@ -52,8 +52,8 @@ export const borderRadius = {
|
||||
*/
|
||||
export const fontWeight = {
|
||||
normal: '400',
|
||||
bold: isAndroid ? '700' : '600',
|
||||
heavy: isAndroid ? '900' : '800',
|
||||
bold: '600',
|
||||
heavy: '800',
|
||||
} as const
|
||||
|
||||
export const gradients = {
|
||||
|
||||
@@ -1,25 +1,62 @@
|
||||
import {isAndroid, isIOS, isNative, isWeb} from 'platform/detection'
|
||||
import {Platform} from 'react-native'
|
||||
|
||||
import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection'
|
||||
|
||||
/**
|
||||
* Identity function on web. Returns nothing on other platforms.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function web(value: any) {
|
||||
if (isWeb) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on iOS. Returns nothing on other platforms.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function ios(value: any) {
|
||||
if (isIOS) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on Android. Returns nothing on other platforms..
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function android(value: any) {
|
||||
if (isAndroid) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identity function on iOS and Android. Returns nothing on web.
|
||||
*
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export function native(value: any) {
|
||||
if (isNative) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Platform splitting does not tree-shake away the other platforms,
|
||||
* so don't do stuff like e.g. rely on platform-specific imports. Use
|
||||
* platform-split files instead.
|
||||
*/
|
||||
export const platform = Platform.select
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import {useFonts} from 'expo-font'
|
||||
|
||||
/*
|
||||
* IMPORTANT: This is unused. Expo statically extracts these fonts.
|
||||
*
|
||||
* All used fonts MUST be configured here. Unused fonts can be commented out.
|
||||
*
|
||||
* This is used for both web fonts and native fonts.
|
||||
*/
|
||||
export function DO_NOT_USE() {
|
||||
return useFonts({
|
||||
'Inter-Regular': require('../../../assets/fonts/inter/Inter-Regular.otf'),
|
||||
'Inter-Italic': require('../../../assets/fonts/inter/Inter-Italic.otf'),
|
||||
'Inter-Bold': require('../../../assets/fonts/inter/Inter-SemiBold.otf'),
|
||||
'Inter-BoldItalic': require('../../../assets/fonts/inter/Inter-SemiBoldItalic.otf'),
|
||||
'Inter-Black': require('../../../assets/fonts/inter/Inter-ExtraBold.otf'),
|
||||
'Inter-BlackItalic': require('../../../assets/fonts/inter/Inter-ExtraBoldItalic.otf'),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import {useFonts} from 'expo-font'
|
||||
|
||||
/*
|
||||
* IMPORTANT: This is unused. Expo statically extracts these fonts.
|
||||
*
|
||||
* All used fonts MUST be configured here. Unused fonts can be commented out.
|
||||
*
|
||||
* This is used for both web fonts and native fonts.
|
||||
*/
|
||||
export function DO_NOT_USE() {
|
||||
return useFonts({
|
||||
InterVariable: require('../../../assets/fonts/inter/InterVariable.ttf'),
|
||||
'InterVariable-Italic': require('../../../assets/fonts/inter/InterVariable-Italic.ttf'),
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, {useCallback} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {AppBskyActorDefs} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useProfileQuery} from '#/state/queries/profile'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfilesQuery} from '#/state/queries/profile'
|
||||
import {type SessionAccount, useSession} from '#/state/session'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
@@ -26,6 +29,9 @@ export function AccountList({
|
||||
const {currentAccount, accounts} = useSession()
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {data: profiles} = useProfilesQuery({
|
||||
handles: accounts.map(acc => acc.did),
|
||||
})
|
||||
|
||||
const onPressAddAccount = useCallback(() => {
|
||||
onSelectOther()
|
||||
@@ -43,6 +49,7 @@ export function AccountList({
|
||||
{accounts.map(account => (
|
||||
<React.Fragment key={account.did}>
|
||||
<AccountItem
|
||||
profile={profiles?.profiles.find(p => p.did === account.did)}
|
||||
account={account}
|
||||
onSelect={onSelectAccount}
|
||||
isCurrentAccount={account.did === currentAccount?.did}
|
||||
@@ -84,11 +91,13 @@ export function AccountList({
|
||||
}
|
||||
|
||||
function AccountItem({
|
||||
profile,
|
||||
account,
|
||||
onSelect,
|
||||
isCurrentAccount,
|
||||
isPendingAccount,
|
||||
}: {
|
||||
profile?: AppBskyActorDefs.ProfileViewDetailed
|
||||
account: SessionAccount
|
||||
onSelect: (account: SessionAccount) => void
|
||||
isCurrentAccount: boolean
|
||||
@@ -96,9 +105,8 @@ function AccountItem({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {data: profile} = useProfileQuery({did: account.did})
|
||||
|
||||
const onPress = React.useCallback(() => {
|
||||
const onPress = useCallback(() => {
|
||||
onSelect(account)
|
||||
}, [account, onSelect])
|
||||
|
||||
@@ -127,10 +135,12 @@ function AccountItem({
|
||||
</View>
|
||||
<Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
|
||||
<Text emoji style={[a.font_bold]}>
|
||||
{profile?.displayName || account.handle}{' '}
|
||||
</Text>
|
||||
{sanitizeDisplayName(
|
||||
profile?.displayName || profile?.handle || account.handle,
|
||||
)}
|
||||
</Text>{' '}
|
||||
<Text emoji style={[t.atoms.text_contrast_medium]}>
|
||||
{account.handle}
|
||||
{sanitizeHandle(account.handle)}
|
||||
</Text>
|
||||
</Text>
|
||||
{isCurrentAccount ? (
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
|
||||
import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye'
|
||||
import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {Text as BaseText, TextProps} from '#/components/Typography'
|
||||
|
||||
export const colors = {
|
||||
warning: {
|
||||
light: '#DFBC00',
|
||||
dark: '#BFAF1F',
|
||||
},
|
||||
}
|
||||
|
||||
type Context = {
|
||||
type: 'info' | 'tip' | 'warning' | 'error'
|
||||
}
|
||||
|
||||
const Context = React.createContext<Context>({
|
||||
type: 'info',
|
||||
})
|
||||
|
||||
export function Icon() {
|
||||
const t = useTheme()
|
||||
const {type} = React.useContext(Context)
|
||||
const Icon = {
|
||||
info: InfoIcon,
|
||||
tip: TipIcon,
|
||||
warning: WarningIcon,
|
||||
error: ErrorIcon,
|
||||
}[type]
|
||||
const fill = {
|
||||
info: t.atoms.text_contrast_medium.color,
|
||||
tip: t.palette.primary_500,
|
||||
warning: colors.warning.light,
|
||||
error: t.palette.negative_500,
|
||||
}[type]
|
||||
return <Icon fill={fill} size="md" />
|
||||
}
|
||||
|
||||
export function Text({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Pick<TextProps, 'children' | 'style'>) {
|
||||
return (
|
||||
<BaseText
|
||||
{...rest}
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_sm,
|
||||
a.leading_snug,
|
||||
{
|
||||
paddingTop: 1,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</BaseText>
|
||||
)
|
||||
}
|
||||
|
||||
export function Row({children}: {children: React.ReactNode}) {
|
||||
return <View style={[a.flex_row, a.gap_sm]}>{children}</View>
|
||||
}
|
||||
|
||||
export function Outer({
|
||||
children,
|
||||
type = 'info',
|
||||
style,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
type?: Context['type']
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const borderColor = {
|
||||
info: t.atoms.border_contrast_low.borderColor,
|
||||
tip: t.atoms.border_contrast_low.borderColor,
|
||||
warning: t.atoms.border_contrast_low.borderColor,
|
||||
error: t.atoms.border_contrast_low.borderColor,
|
||||
}[type]
|
||||
return (
|
||||
<Context.Provider value={{type}}>
|
||||
<View
|
||||
style={[
|
||||
gtMobile ? a.p_md : a.p_sm,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.bg_contrast_25,
|
||||
{borderColor},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function Admonition({
|
||||
children,
|
||||
type,
|
||||
style,
|
||||
}: {
|
||||
children: TextProps['children']
|
||||
type?: Context['type']
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<Outer type={type} style={style}>
|
||||
<Row>
|
||||
<Icon />
|
||||
<Text>{children}</Text>
|
||||
</Row>
|
||||
</Outer>
|
||||
)
|
||||
}
|
||||
@@ -7,10 +7,10 @@ import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
|
||||
export function AppLanguageDropdown() {
|
||||
export function AppLanguageDropdown(_props: ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -6,11 +6,11 @@ import {sanitizeAppLanguageSetting} from '#/locale/helpers'
|
||||
import {APP_LANGUAGES} from '#/locale/languages'
|
||||
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
|
||||
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
|
||||
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function AppLanguageDropdown() {
|
||||
export function AppLanguageDropdown({style}: ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
|
||||
const queryClient = useQueryClient()
|
||||
@@ -40,6 +40,7 @@ export function AppLanguageDropdown() {
|
||||
// We don't have hitSlop here to increase the tap region,
|
||||
// alternative is negative margins.
|
||||
{height: 32, marginVertical: -((32 - 14) / 2)},
|
||||
style,
|
||||
]}>
|
||||
<View
|
||||
style={[
|
||||
|
||||
@@ -670,7 +670,7 @@ export function ButtonIcon({
|
||||
size ??
|
||||
(({
|
||||
large: 'sm',
|
||||
small: 'xs',
|
||||
small: 'sm',
|
||||
tiny: 'xs',
|
||||
}[buttonSize || 'small'] || 'sm') as Exclude<
|
||||
SVGIconProps['size'],
|
||||
|
||||
@@ -11,13 +11,16 @@ import {
|
||||
} from 'react-native'
|
||||
import {
|
||||
KeyboardAwareScrollView,
|
||||
useKeyboardController,
|
||||
useKeyboardHandler,
|
||||
} from 'react-native-keyboard-controller'
|
||||
import {runOnJS} from 'react-native-reanimated'
|
||||
import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/reanimated2/hook/commonTypes'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {ScrollProvider} from '#/lib/ScrollContext'
|
||||
import {logger} from '#/logger'
|
||||
import {isAndroid, isIOS} from '#/platform/detection'
|
||||
import {useA11y} from '#/state/a11y'
|
||||
@@ -39,6 +42,7 @@ import {
|
||||
import {BottomSheetNativeComponent} from '../../../modules/bottom-sheet/src/BottomSheetNativeComponent'
|
||||
|
||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||
export * from '#/components/Dialog/shared'
|
||||
export * from '#/components/Dialog/types'
|
||||
export * from '#/components/Dialog/utils'
|
||||
// @ts-ignore
|
||||
@@ -168,28 +172,48 @@ export function Outer({
|
||||
)
|
||||
}
|
||||
|
||||
export function Inner({children, style}: DialogInnerProps) {
|
||||
export function Inner({children, style, header}: DialogInnerProps) {
|
||||
const insets = useSafeAreaInsets()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.px_xl,
|
||||
{
|
||||
paddingBottom: insets.bottom + insets.top,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
<>
|
||||
{header}
|
||||
<View
|
||||
style={[
|
||||
a.pt_2xl,
|
||||
a.px_xl,
|
||||
{
|
||||
paddingBottom: insets.bottom + insets.top,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
function ScrollableInner({children, style, ...props}, ref) {
|
||||
function ScrollableInner(
|
||||
{children, style, contentContainerStyle, header, ...props},
|
||||
ref,
|
||||
) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||
const insets = useSafeAreaInsets()
|
||||
const {setEnabled} = useKeyboardController()
|
||||
|
||||
const [keyboardHeight, setKeyboardHeight] = React.useState(0)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isIOS) {
|
||||
return
|
||||
}
|
||||
|
||||
setEnabled(true)
|
||||
return () => {
|
||||
setEnabled(false)
|
||||
}
|
||||
})
|
||||
|
||||
useKeyboardHandler({
|
||||
onEnd: e => {
|
||||
'worklet'
|
||||
@@ -206,6 +230,9 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
nativeSnapPoint === BottomSheetSnapPoint.Full ? fullPadding : basePading
|
||||
|
||||
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (!isAndroid) {
|
||||
return
|
||||
}
|
||||
const {contentOffset} = e.nativeEvent
|
||||
if (contentOffset.y > 0 && !disableDrag) {
|
||||
setDisableDrag(true)
|
||||
@@ -217,13 +244,21 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
return (
|
||||
<KeyboardAwareScrollView
|
||||
style={[style]}
|
||||
contentContainerStyle={[a.pt_2xl, a.px_xl, {paddingBottom}]}
|
||||
contentContainerStyle={[
|
||||
a.pt_2xl,
|
||||
a.px_xl,
|
||||
{paddingBottom},
|
||||
contentContainerStyle,
|
||||
]}
|
||||
ref={ref}
|
||||
{...props}
|
||||
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
||||
bottomOffset={30}
|
||||
scrollEventThrottle={50}
|
||||
onScroll={isAndroid ? onScroll : undefined}>
|
||||
onScroll={isAndroid ? onScroll : undefined}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
stickyHeaderIndices={header ? [0] : undefined}>
|
||||
{header}
|
||||
{children}
|
||||
</KeyboardAwareScrollView>
|
||||
)
|
||||
@@ -232,21 +267,40 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
|
||||
export const InnerFlatList = React.forwardRef<
|
||||
ListMethods,
|
||||
ListProps<any> & {webInnerStyle?: StyleProp<ViewStyle>}
|
||||
ListProps<any> & {
|
||||
webInnerStyle?: StyleProp<ViewStyle>
|
||||
webInnerContentContainerStyle?: StyleProp<ViewStyle>
|
||||
}
|
||||
>(function InnerFlatList({style, ...props}, ref) {
|
||||
const insets = useSafeAreaInsets()
|
||||
const {nativeSnapPoint} = useDialogContext()
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||
|
||||
const onScroll = (e: ReanimatedScrollEvent) => {
|
||||
'worklet'
|
||||
if (!isAndroid) {
|
||||
return
|
||||
}
|
||||
const {contentOffset} = e
|
||||
if (contentOffset.y > 0 && !disableDrag) {
|
||||
runOnJS(setDisableDrag)(true)
|
||||
} else if (contentOffset.y <= 1 && disableDrag) {
|
||||
runOnJS(setDisableDrag)(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<List
|
||||
keyboardShouldPersistTaps="handled"
|
||||
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
||||
ListFooterComponent={
|
||||
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
|
||||
}
|
||||
ref={ref}
|
||||
{...props}
|
||||
style={[style]}
|
||||
/>
|
||||
<ScrollProvider onScroll={onScroll}>
|
||||
<List
|
||||
keyboardShouldPersistTaps="handled"
|
||||
bounces={nativeSnapPoint === BottomSheetSnapPoint.Full}
|
||||
ListFooterComponent={
|
||||
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
|
||||
}
|
||||
ref={ref}
|
||||
{...props}
|
||||
style={[style]}
|
||||
/>
|
||||
</ScrollProvider>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||
import {Portal} from '#/components/Portal'
|
||||
|
||||
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||
export * from '#/components/Dialog/shared'
|
||||
export * from '#/components/Dialog/types'
|
||||
export * from '#/components/Dialog/utils'
|
||||
export {Input} from '#/components/forms/TextField'
|
||||
@@ -154,6 +155,8 @@ export function Inner({
|
||||
label,
|
||||
accessibilityLabelledBy,
|
||||
accessibilityDescribedBy,
|
||||
header,
|
||||
contentContainerStyle,
|
||||
}: DialogInnerProps) {
|
||||
const t = useTheme()
|
||||
const {close} = React.useContext(Context)
|
||||
@@ -178,7 +181,6 @@ export function Inner({
|
||||
a.rounded_md,
|
||||
a.w_full,
|
||||
a.border,
|
||||
gtMobile ? a.p_2xl : a.p_xl,
|
||||
t.atoms.bg,
|
||||
{
|
||||
maxWidth: 600,
|
||||
@@ -194,7 +196,10 @@ export function Inner({
|
||||
onFocusOutside={preventDefault}
|
||||
onDismiss={close}
|
||||
style={{display: 'flex', flexDirection: 'column'}}>
|
||||
{children}
|
||||
{header}
|
||||
<View style={[gtMobile ? a.p_2xl : a.p_xl, contentContainerStyle]}>
|
||||
{children}
|
||||
</View>
|
||||
</DismissableLayer>
|
||||
</Animated.View>
|
||||
</FocusScope>
|
||||
@@ -205,21 +210,26 @@ export const ScrollableInner = Inner
|
||||
|
||||
export const InnerFlatList = React.forwardRef<
|
||||
FlatList,
|
||||
FlatListProps<any> & {label: string} & {webInnerStyle?: StyleProp<ViewStyle>}
|
||||
>(function InnerFlatList({label, style, webInnerStyle, ...props}, ref) {
|
||||
FlatListProps<any> & {label: string} & {
|
||||
webInnerStyle?: StyleProp<ViewStyle>
|
||||
webInnerContentContainerStyle?: StyleProp<ViewStyle>
|
||||
}
|
||||
>(function InnerFlatList(
|
||||
{label, style, webInnerStyle, webInnerContentContainerStyle, ...props},
|
||||
ref,
|
||||
) {
|
||||
const {gtMobile} = useBreakpoints()
|
||||
return (
|
||||
<Inner
|
||||
label={label}
|
||||
style={[
|
||||
a.overflow_hidden,
|
||||
a.px_0,
|
||||
// @ts-ignore web only -sfn
|
||||
{
|
||||
paddingHorizontal: 0,
|
||||
maxHeight: 'calc(-36px + 100vh)',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
{maxHeight: 'calc(-36px + 100vh)'},
|
||||
webInnerStyle,
|
||||
]}>
|
||||
]}
|
||||
contentContainerStyle={[a.px_0, webInnerContentContainerStyle]}>
|
||||
<FlatList
|
||||
ref={ref}
|
||||
style={[gtMobile ? a.px_2xl : a.px_xl, flatten(style)]}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from 'react'
|
||||
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function Header({
|
||||
renderLeft,
|
||||
renderRight,
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
renderLeft?: () => React.ReactNode
|
||||
renderRight?: () => React.ReactNode
|
||||
children?: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.relative,
|
||||
a.w_full,
|
||||
a.py_sm,
|
||||
a.flex_row,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
{minHeight: 50},
|
||||
a.border_b,
|
||||
t.atoms.border_contrast_medium,
|
||||
t.atoms.bg,
|
||||
web([
|
||||
{borderRadiusTopLeft: a.rounded_md.borderRadius},
|
||||
{borderRadiusTopRight: a.rounded_md.borderRadius},
|
||||
]),
|
||||
style,
|
||||
]}>
|
||||
{renderLeft && (
|
||||
<View style={[a.absolute, {left: 6}]}>{renderLeft()}</View>
|
||||
)}
|
||||
{children}
|
||||
{renderRight && (
|
||||
<View style={[a.absolute, {right: 6}]}>{renderRight()}</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function HeaderText({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
style?: StyleProp<TextStyle>
|
||||
}) {
|
||||
return (
|
||||
<Text style={[a.text_lg, a.text_center, a.font_bold, style]}>
|
||||
{children}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import type {
|
||||
GestureResponderEvent,
|
||||
ScrollViewProps,
|
||||
} from 'react-native'
|
||||
import {ViewStyle} from 'react-native'
|
||||
import {StyleProp} from 'react-native'
|
||||
|
||||
import {ViewStyleProp} from '#/alf'
|
||||
import {BottomSheetViewProps} from '../../../modules/bottom-sheet'
|
||||
@@ -69,10 +71,14 @@ export type DialogInnerProps =
|
||||
accessibilityLabelledBy: A11yProps['aria-labelledby']
|
||||
accessibilityDescribedBy: string
|
||||
keyboardDismissMode?: ScrollViewProps['keyboardDismissMode']
|
||||
contentContainerStyle?: StyleProp<ViewStyle>
|
||||
header?: React.ReactNode
|
||||
}>
|
||||
| DialogInnerPropsBase<{
|
||||
label: string
|
||||
accessibilityLabelledBy?: undefined
|
||||
accessibilityDescribedBy?: undefined
|
||||
keyboardDismissMode?: ScrollViewProps['keyboardDismissMode']
|
||||
contentContainerStyle?: StyleProp<ViewStyle>
|
||||
header?: React.ReactNode
|
||||
}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {atoms as a, useTheme, ViewStyleProp, flatten} from '#/alf'
|
||||
|
||||
import {atoms as a, flatten, useTheme, ViewStyleProp} from '#/alf'
|
||||
|
||||
export function Divider({style}: ViewStyleProp) {
|
||||
const t = useTheme()
|
||||
|
||||
@@ -3,8 +3,8 @@ import {View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useGoBack} from 'lib/hooks/useGoBack'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {useGoBack} from '#/lib/hooks/useGoBack'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import React, {useContext, useMemo} from 'react'
|
||||
import {View, ViewStyle} from 'react-native'
|
||||
import {StyleProp} from 'react-native'
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||
|
||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||
import {ScrollView} from '#/view/com/util/Views'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a} from '#/alf'
|
||||
|
||||
// Every screen should have a Layout component wrapping it.
|
||||
// This component provides a default padding for the top of the screen.
|
||||
// This allows certain screens to avoid the top padding if they want to.
|
||||
|
||||
const LayoutContext = React.createContext({
|
||||
withinScreen: false,
|
||||
topPaddingDisabled: false,
|
||||
withinScrollView: false,
|
||||
})
|
||||
|
||||
/**
|
||||
* Every screen should have a Layout.Screen component wrapping it.
|
||||
* This component provides a default padding for the top of the screen
|
||||
* and height/minHeight
|
||||
*/
|
||||
let Screen = ({
|
||||
disableTopPadding = false,
|
||||
style,
|
||||
...props
|
||||
}: React.ComponentProps<typeof View> & {
|
||||
disableTopPadding?: boolean
|
||||
style?: StyleProp<ViewStyle>
|
||||
}): React.ReactNode => {
|
||||
const {top} = useSafeAreaInsets()
|
||||
const context = useMemo(
|
||||
() => ({
|
||||
withinScreen: true,
|
||||
topPaddingDisabled: disableTopPadding,
|
||||
withinScrollView: false,
|
||||
}),
|
||||
[disableTopPadding],
|
||||
)
|
||||
return (
|
||||
<LayoutContext.Provider value={context}>
|
||||
<View
|
||||
style={[
|
||||
{paddingTop: disableTopPadding ? 0 : top},
|
||||
a.util_screen_outer,
|
||||
style,
|
||||
]}
|
||||
{...props}
|
||||
/>
|
||||
</LayoutContext.Provider>
|
||||
)
|
||||
}
|
||||
Screen = React.memo(Screen)
|
||||
export {Screen}
|
||||
|
||||
let Header = (
|
||||
props: React.ComponentProps<typeof ViewHeader>,
|
||||
): React.ReactNode => {
|
||||
const {withinScrollView} = useContext(LayoutContext)
|
||||
if (!withinScrollView) {
|
||||
return (
|
||||
<CenteredView topBorder={false} sideBorders>
|
||||
<ViewHeader showOnDesktop showBorder {...props} />
|
||||
</CenteredView>
|
||||
)
|
||||
} else {
|
||||
return <ViewHeader showOnDesktop showBorder {...props} />
|
||||
}
|
||||
}
|
||||
Header = React.memo(Header)
|
||||
export {Header}
|
||||
|
||||
let Content = ({
|
||||
style,
|
||||
contentContainerStyle,
|
||||
...props
|
||||
}: React.ComponentProps<typeof ScrollView> & {
|
||||
style?: StyleProp<ViewStyle>
|
||||
contentContainerStyle?: StyleProp<ViewStyle>
|
||||
}): React.ReactNode => {
|
||||
const context = useContext(LayoutContext)
|
||||
const newContext = useMemo(
|
||||
() => ({...context, withinScrollView: true}),
|
||||
[context],
|
||||
)
|
||||
return (
|
||||
<LayoutContext.Provider value={newContext}>
|
||||
<ScrollView
|
||||
style={[a.flex_1, style]}
|
||||
contentContainerStyle={[{paddingBottom: 100}, contentContainerStyle]}
|
||||
{...props}
|
||||
/>
|
||||
</LayoutContext.Provider>
|
||||
)
|
||||
}
|
||||
Content = React.memo(Content)
|
||||
export {Content}
|
||||
@@ -3,11 +3,11 @@ import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {useLikedByQuery} from '#/state/queries/post-liked-by'
|
||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
|
||||
import {List} from '#/view/com/util/List'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
} from 'react-native'
|
||||
import {GestureResponderEvent} from 'react-native'
|
||||
import {sanitizeUrl} from '@braintree/sanitize-url'
|
||||
import {StackActions, useLinkProps} from '@react-navigation/native'
|
||||
|
||||
import {BSKY_DOWNLOAD_URL} from '#/lib/constants'
|
||||
import {useNavigationDeduped} from '#/lib/hooks/useNavigationDeduped'
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {AllNavigatorParams} from '#/lib/routes/types'
|
||||
import {shareUrl} from '#/lib/sharing'
|
||||
import {
|
||||
@@ -21,7 +17,6 @@ import {
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {shouldClickOpenNewTab} from '#/platform/urls'
|
||||
import {useModalControls} from '#/state/modals'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {atoms as a, flatten, TextStyleProp, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonProps} from '#/components/Button'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
@@ -330,45 +325,22 @@ export function InlineLinkText({
|
||||
}
|
||||
|
||||
/**
|
||||
* A Pressable that uses useLink to handle navigation. It is unstyled, so can be used in cases where the Button styles
|
||||
* in Link are not desired.
|
||||
* @param displayText
|
||||
* @param style
|
||||
* @param children
|
||||
* @param rest
|
||||
* @constructor
|
||||
* Utility to create a static `onPress` handler for a `Link` that would otherwise link to a URI
|
||||
*
|
||||
* Example:
|
||||
* `<Link {...createStaticClick(e => {...})} />`
|
||||
*/
|
||||
export function BaseLink({
|
||||
displayText,
|
||||
onPress: onPressOuter,
|
||||
style,
|
||||
children,
|
||||
...rest
|
||||
}: {
|
||||
style?: StyleProp<ViewStyle>
|
||||
children: React.ReactNode
|
||||
to: string
|
||||
action: 'push' | 'replace' | 'navigate'
|
||||
onPress?: () => false | void
|
||||
shareOnLongPress?: boolean
|
||||
label: string
|
||||
displayText?: string
|
||||
}) {
|
||||
const {onPress, ...btnProps} = useLink({
|
||||
displayText: displayText ?? rest.to,
|
||||
...rest,
|
||||
})
|
||||
return (
|
||||
<Pressable
|
||||
style={style}
|
||||
onPress={e => {
|
||||
onPressOuter?.()
|
||||
onPress(e)
|
||||
}}
|
||||
{...btnProps}>
|
||||
{children}
|
||||
</Pressable>
|
||||
)
|
||||
export function createStaticClick(
|
||||
onPressHandler: Exclude<BaseLinkProps['onPress'], undefined>,
|
||||
): Pick<BaseLinkProps, 'to' | 'onPress'> {
|
||||
return {
|
||||
to: '#',
|
||||
onPress(e: GestureResponderEvent) {
|
||||
e.preventDefault()
|
||||
onPressHandler(e)
|
||||
return false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function WebOnlyInlineLinkText({
|
||||
|
||||
@@ -3,8 +3,8 @@ import {StyleProp, View, ViewStyle} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {cleanError} from 'lib/strings/errors'
|
||||
import {CenteredView} from 'view/com/util/Views'
|
||||
import {cleanError} from '#/lib/strings/errors'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {Error} from '#/components/Error'
|
||||
|
||||
@@ -127,7 +127,7 @@ export function NewskieDialog({
|
||||
}}>
|
||||
<View
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.w_full,
|
||||
a.mt_sm,
|
||||
a.p_lg,
|
||||
a.border,
|
||||
|
||||
@@ -5,15 +5,15 @@ import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom'
|
||||
import {msg, plural} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {isTouchDevice} from '#/lib/browser'
|
||||
import {getModerationCauseKey} from '#/lib/moderation'
|
||||
import {makeProfileLink} from '#/lib/routes/links'
|
||||
import {sanitizeDisplayName} from '#/lib/strings/display-names'
|
||||
import {sanitizeHandle} from '#/lib/strings/handles'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {usePrefetchProfileQuery, useProfileQuery} from '#/state/queries/profile'
|
||||
import {useSession} from '#/state/session'
|
||||
import {isTouchDevice} from 'lib/browser'
|
||||
import {useProfileShadow} from 'state/cache/profile-shadow'
|
||||
import {formatCount} from '#/view/com/util/numeric/format'
|
||||
import {UserAvatar} from '#/view/com/util/UserAvatar'
|
||||
import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle'
|
||||
|
||||
@@ -128,7 +128,8 @@ export function RichText({
|
||||
// @ts-ignore TODO
|
||||
dataSet={WORD_WRAP}
|
||||
shareOnLongPress
|
||||
onPress={onLinkPress}>
|
||||
onPress={onLinkPress}
|
||||
emoji>
|
||||
{toShortUrl(segment.text)}
|
||||
</InlineLinkText>,
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ import {ListRenderItemInfo, View} from 'react-native'
|
||||
import {AppBskyFeedDefs} from '@atproto/api'
|
||||
import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
|
||||
|
||||
import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import {List, ListRef} from 'view/com/util/List'
|
||||
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {List, ListRef} from '#/view/com/util/List'
|
||||
import {SectionRef} from '#/screens/Profile/Sections/types'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as FeedCard from '#/components/FeedCard'
|
||||
|
||||
@@ -3,11 +3,11 @@ import {View} from 'react-native'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {FeedDescriptor} from '#/state/queries/post-feed'
|
||||
import {isNative} from 'platform/detection'
|
||||
import {Feed} from 'view/com/posts/Feed'
|
||||
import {EmptyState} from 'view/com/util/EmptyState'
|
||||
import {ListRef} from 'view/com/util/List'
|
||||
import {Feed} from '#/view/com/posts/Feed'
|
||||
import {EmptyState} from '#/view/com/util/EmptyState'
|
||||
import {ListRef} from '#/view/com/util/List'
|
||||
import {SectionRef} from '#/screens/Profile/Sections/types'
|
||||
|
||||
interface ProfilesListProps {
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
} from '@atproto/api'
|
||||
import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
|
||||
|
||||
import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
|
||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||
import {isBlockedOrBlocking} from 'lib/moderation/blocked-and-muted'
|
||||
import {isNative, isWeb} from 'platform/detection'
|
||||
import {useAllListMembersQuery} from 'state/queries/list-members'
|
||||
import {useSession} from 'state/session'
|
||||
import {List, ListRef} from 'view/com/util/List'
|
||||
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted'
|
||||
import {isNative, isWeb} from '#/platform/detection'
|
||||
import {useAllListMembersQuery} from '#/state/queries/list-members'
|
||||
import {useSession} from '#/state/session'
|
||||
import {List, ListRef} from '#/view/com/util/List'
|
||||
import {SectionRef} from '#/screens/Profile/Sections/types'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
|
||||
|
||||
@@ -13,7 +13,7 @@ import {precacheStarterPack} from '#/state/queries/starter-packs'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {StarterPack} from '#/components/icons/StarterPack'
|
||||
import {BaseLink} from '#/components/Link'
|
||||
import {Link as BaseLink, LinkProps as BaseLinkProps} from '#/components/Link'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
export function Default({
|
||||
@@ -62,14 +62,20 @@ export function Card({
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.flex_1, a.gap_md]}>
|
||||
<View style={[a.flex_row, a.gap_sm]}>
|
||||
<View style={[a.w_full, a.gap_md]}>
|
||||
<View style={[a.flex_row, a.gap_sm, a.w_full]}>
|
||||
{!noIcon ? <StarterPack width={40} gradient="sky" /> : null}
|
||||
<View>
|
||||
<Text emoji style={[a.text_md, a.font_bold, a.leading_snug]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.text_md, a.font_bold, a.leading_snug]}
|
||||
numberOfLines={2}>
|
||||
{record.name}
|
||||
</Text>
|
||||
<Text emoji style={[a.leading_snug, t.atoms.text_contrast_medium]}>
|
||||
<Text
|
||||
emoji
|
||||
style={[a.leading_snug, t.atoms.text_contrast_medium]}
|
||||
numberOfLines={1}>
|
||||
{creator?.did === currentAccount?.did
|
||||
? _(msg`Starter pack by you`)
|
||||
: _(msg`Starter pack by ${sanitizeHandle(creator.handle, '@')}`)}
|
||||
@@ -96,7 +102,7 @@ export function Link({
|
||||
}: {
|
||||
starterPack: AppBskyGraphDefs.StarterPackViewBasic
|
||||
onPress?: () => void
|
||||
children: React.ReactNode
|
||||
children: BaseLinkProps['children']
|
||||
}) {
|
||||
const {_} = useLingui()
|
||||
const queryClient = useQueryClient()
|
||||
@@ -123,7 +129,8 @@ export function Link({
|
||||
starterPack.creator.did,
|
||||
)
|
||||
precacheStarterPack(queryClient, starterPack)
|
||||
}}>
|
||||
}}
|
||||
style={[a.flex_col, a.align_start]}>
|
||||
{children}
|
||||
</BaseLink>
|
||||
)
|
||||
@@ -140,7 +147,6 @@ export function Embed({
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.mt_xs,
|
||||
a.border,
|
||||
a.rounded_sm,
|
||||
a.overflow_hidden,
|
||||
|
||||
@@ -7,7 +7,7 @@ import Animated, {
|
||||
SlideInRight,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
|
||||
export function ScreenTransition({
|
||||
direction,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function SubtleWebHover({}: {hover: boolean}) {
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from 'react'
|
||||
import {StyleSheet, View} from 'react-native'
|
||||
|
||||
import {isTouchDevice} from '#/lib/browser'
|
||||
import {useTheme} from '#/alf'
|
||||
|
||||
export function SubtleWebHover({hover}: {hover: boolean}) {
|
||||
const t = useTheme()
|
||||
if (isTouchDevice) {
|
||||
return null
|
||||
}
|
||||
let opacity: number
|
||||
switch (t.name) {
|
||||
case 'dark':
|
||||
opacity = 0.4
|
||||
break
|
||||
case 'dim':
|
||||
opacity = 0.45
|
||||
break
|
||||
case 'light':
|
||||
opacity = 0.5
|
||||
break
|
||||
}
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
t.atoms.bg_contrast_25,
|
||||
styles.container,
|
||||
{
|
||||
opacity: hover ? opacity : 0,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
pointerEvents: 'none',
|
||||
// @ts-ignore web only
|
||||
transition: '0.15s ease-in-out opacity',
|
||||
},
|
||||
})
|
||||
@@ -4,7 +4,6 @@ import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {makeSearchLink} from '#/lib/routes/links'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {isInvalidHandle} from '#/lib/strings/handles'
|
||||
import {
|
||||
@@ -19,7 +18,7 @@ import {Divider} from '#/components/Divider'
|
||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
|
||||
import {Mute_Stroke2_Corner0_Rounded as Mute} from '#/components/icons/Mute'
|
||||
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
|
||||
import {Link} from '#/components/Link'
|
||||
import {createStaticClick, Link} from '#/components/Link'
|
||||
import {Loader} from '#/components/Loader'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -101,19 +100,14 @@ export function TagMenu({
|
||||
t.atoms.bg_contrast_25,
|
||||
]}>
|
||||
<Link
|
||||
label={_(msg`Search for all posts with tag ${displayTag}`)}
|
||||
to={makeSearchLink({query: displayTag})}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
|
||||
label={_(msg`View all posts with tag ${displayTag}`)}
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
})
|
||||
})
|
||||
|
||||
return false
|
||||
}}>
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
@@ -152,24 +146,16 @@ export function TagMenu({
|
||||
|
||||
<Link
|
||||
label={_(
|
||||
msg`Search for all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
msg`View all posts by @${authorHandle} with tag ${displayTag}`,
|
||||
)}
|
||||
to={makeSearchLink({
|
||||
query: displayTag,
|
||||
from: authorHandle,
|
||||
})}
|
||||
onPress={e => {
|
||||
e.preventDefault()
|
||||
|
||||
{...createStaticClick(() => {
|
||||
control.close(() => {
|
||||
navigation.push('Hashtag', {
|
||||
tag: encodeURIComponent(tag),
|
||||
author: authorHandle,
|
||||
})
|
||||
})
|
||||
|
||||
return false
|
||||
}}>
|
||||
})}>
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
|
||||
@@ -29,7 +29,7 @@ export function BirthDateSettingsDialog({
|
||||
const {isLoading, error, data: preferences} = usePreferencesQuery()
|
||||
|
||||
return (
|
||||
<Dialog.Outer control={control}>
|
||||
<Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Handle />
|
||||
<Dialog.ScrollableInner label={_(msg`My Birthday`)}>
|
||||
<View style={[a.gap_sm, a.pb_lg]}>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
|
||||
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
|
||||
import {ListMethods} from '#/view/com/util/List'
|
||||
import {atoms as a, ios, native, useBreakpoints, useTheme} from '#/alf'
|
||||
import {atoms as a, ios, native, useBreakpoints, useTheme, web} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import * as TextField from '#/components/forms/TextField'
|
||||
@@ -37,7 +37,7 @@ export function GifSelectDialog({
|
||||
onSelectGif: onSelectGifProp,
|
||||
}: {
|
||||
controlRef: React.RefObject<{open: () => void}>
|
||||
onClose: () => void
|
||||
onClose?: () => void
|
||||
onSelectGif: (gif: Gif) => void
|
||||
}) {
|
||||
const control = Dialog.useDialogControl()
|
||||
@@ -144,7 +144,7 @@ function GifList({
|
||||
a.mb_lg,
|
||||
a.flex_row,
|
||||
a.align_center,
|
||||
!gtMobile && isWeb && a.gap_md,
|
||||
!gtMobile && web(a.gap_md),
|
||||
]}>
|
||||
{/* cover top corners */}
|
||||
<View
|
||||
@@ -205,7 +205,11 @@ function GifList({
|
||||
renderItem={renderItem}
|
||||
numColumns={gtMobile ? 3 : 2}
|
||||
columnWrapperStyle={[a.gap_sm]}
|
||||
contentContainerStyle={[native([a.px_xl, {minHeight: height}])]}
|
||||
contentContainerStyle={[
|
||||
native([a.px_xl, {minHeight: height}]),
|
||||
web(a.h_full_vh),
|
||||
]}
|
||||
style={[web(a.h_full_vh)]}
|
||||
ListHeaderComponent={
|
||||
<>
|
||||
{listHeader}
|
||||
@@ -235,8 +239,6 @@ function GifList({
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
keyExtractor={(item: Gif) => item.id}
|
||||
// @ts-expect-error web only
|
||||
style={isWeb && {minHeight: '100vh'}}
|
||||
keyboardDismissMode="on-drag"
|
||||
ListFooterComponent={
|
||||
hasData ? (
|
||||
|
||||
@@ -56,6 +56,8 @@ export function SwitchAccountDialog({
|
||||
pendingDid={pendingDid}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Dialog.Close />
|
||||
</Dialog.ScrollableInner>
|
||||
</Dialog.Outer>
|
||||
)
|
||||
|
||||
@@ -12,8 +12,8 @@ import {ChatBskyConvoDefs} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {HITSLOP_10} from 'lib/constants'
|
||||
import {useHaptics} from 'lib/haptics'
|
||||
import {HITSLOP_10} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {MessageMenu} from '#/components/dms/MessageMenu'
|
||||
import {useMenuControl} from '#/components/Menu'
|
||||
|
||||
@@ -4,7 +4,7 @@ import {ModerationCause} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {listUriToHref} from 'lib/strings/url-helpers'
|
||||
import {listUriToHref} from '#/lib/strings/url-helpers'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import * as Dialog from '#/components/Dialog'
|
||||
import {DialogControlProps} from '#/components/Dialog'
|
||||
|
||||
@@ -9,10 +9,10 @@ import Animated, {
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {ScaleAndFadeIn} from 'lib/custom-animations/ScaleAndFade'
|
||||
import {ShrinkAndPop} from 'lib/custom-animations/ShrinkAndPop'
|
||||
import {useHaptics} from 'lib/haptics'
|
||||
import {isWeb} from 'platform/detection'
|
||||
import {ScaleAndFadeIn} from '#/lib/custom-animations/ScaleAndFade'
|
||||
import {ShrinkAndPop} from '#/lib/custom-animations/ShrinkAndPop'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {NavigationProp} from 'lib/routes/types'
|
||||
import {isNative} from 'platform/detection'
|
||||
import {useLeaveConvo} from 'state/queries/messages/leave-conversation'
|
||||
import * as Toast from 'view/com/util/Toast'
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {isNative} from '#/platform/detection'
|
||||
import {useLeaveConvo} from '#/state/queries/messages/leave-conversation'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {DialogOuterProps} from '#/components/Dialog'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ import {ChatBskyConvoDefs, RichText} from '@atproto/api'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useOpenLink} from '#/lib/hooks/useOpenLink'
|
||||
import {richTextToString} from '#/lib/strings/rich-text-helpers'
|
||||
import {getTranslatorLink} from '#/locale/helpers'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useConvoActive} from '#/state/messages/convo'
|
||||
import {useLanguagePrefs} from '#/state/preferences'
|
||||
import {useOpenLink} from '#/state/preferences/in-app-browser'
|
||||
import {useSession} from '#/state/session'
|
||||
import * as Toast from '#/view/com/util/Toast'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
|
||||
@@ -4,8 +4,8 @@ import {AppBskyActorDefs, ModerationCause} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
|
||||
import {useProfileShadow} from 'state/cache/profile-shadow'
|
||||
import {useProfileBlockMutationQueue} from 'state/queries/profile'
|
||||
import {useProfileShadow} from '#/state/cache/profile-shadow'
|
||||
import {useProfileBlockMutationQueue} from '#/state/queries/profile'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import {useDialogControl} from '#/components/Dialog'
|
||||
|
||||
@@ -12,9 +12,9 @@ import {Trans} from '@lingui/macro'
|
||||
import {
|
||||
ScaleAndFadeIn,
|
||||
ScaleAndFadeOut,
|
||||
} from 'lib/custom-animations/ScaleAndFade'
|
||||
import {useHaptics} from 'lib/haptics'
|
||||
import {isAndroid, isIOS, isWeb} from 'platform/detection'
|
||||
} from '#/lib/custom-animations/ScaleAndFade'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {isAndroid, isIOS, isWeb} from '#/platform/detection'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
|
||||
import {HITSLOP_20} from '#/lib/constants'
|
||||
import {mergeRefs} from '#/lib/merge-refs'
|
||||
import {android, atoms as a, useTheme, web} from '#/alf'
|
||||
import {android, atoms as a, TextStyleProp, useTheme, web} from '#/alf'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Text} from '#/components/Typography'
|
||||
@@ -123,6 +123,11 @@ export function useSharedInputStyles() {
|
||||
|
||||
export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {
|
||||
label: string
|
||||
/**
|
||||
* @deprecated Controlled inputs are *strongly* discouraged. Use `defaultValue` instead where possible.
|
||||
*
|
||||
* See https://github.com/facebook/react-native-website/pull/4247
|
||||
*/
|
||||
value?: string
|
||||
onChangeText?: (value: string) => void
|
||||
isInvalid?: boolean
|
||||
@@ -308,16 +313,20 @@ export function SuffixText({
|
||||
children,
|
||||
label,
|
||||
accessibilityHint,
|
||||
}: React.PropsWithChildren<{
|
||||
label: string
|
||||
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||
}>) {
|
||||
style,
|
||||
}: React.PropsWithChildren<
|
||||
TextStyleProp & {
|
||||
label: string
|
||||
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
||||
}
|
||||
>) {
|
||||
const t = useTheme()
|
||||
const ctx = React.useContext(Context)
|
||||
return (
|
||||
<Text
|
||||
accessibilityLabel={label}
|
||||
accessibilityHint={accessibilityHint}
|
||||
numberOfLines={1}
|
||||
style={[
|
||||
a.z_20,
|
||||
a.pr_sm,
|
||||
@@ -334,6 +343,7 @@ export function SuffixText({
|
||||
color: t.palette.contrast_800,
|
||||
}
|
||||
: {},
|
||||
style,
|
||||
]}>
|
||||
{children}
|
||||
</Text>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function Group({children, multiple, ...props}: GroupProps) {
|
||||
|
||||
export function Button({children, ...props}: ItemProps) {
|
||||
return (
|
||||
<Toggle.Item {...props} style={[a.flex_grow]}>
|
||||
<Toggle.Item {...props} style={[a.flex_grow, a.flex_1]}>
|
||||
<ButtonInner>{children}</ButtonInner>
|
||||
</Toggle.Item>
|
||||
)
|
||||
|
||||
@@ -12,16 +12,19 @@ import {formatDistance, Locale} from 'date-fns'
|
||||
import {
|
||||
ca,
|
||||
de,
|
||||
enGB,
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
zhCN,
|
||||
@@ -39,17 +42,20 @@ const locales: Record<AppLanguage, Locale | undefined> = {
|
||||
en: undefined,
|
||||
ca,
|
||||
de,
|
||||
['en-GB']: enGB,
|
||||
es,
|
||||
fi,
|
||||
fr,
|
||||
ga: undefined,
|
||||
hi,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
['pt-BR']: ptBR,
|
||||
ru,
|
||||
th,
|
||||
tr,
|
||||
uk,
|
||||
['zh-CN']: zhCN,
|
||||
|
||||
@@ -3,10 +3,10 @@ import React from 'react'
|
||||
import {
|
||||
createStarterPackLinkFromAndroidReferrer,
|
||||
httpStarterPackUriToAtUri,
|
||||
} from 'lib/strings/starter-pack'
|
||||
import {isAndroid} from 'platform/detection'
|
||||
import {useHasCheckedForStarterPack} from 'state/preferences/used-starter-packs'
|
||||
import {useSetActiveStarterPack} from 'state/shell/starter-pack'
|
||||
} from '#/lib/strings/starter-pack'
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
import {useHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs'
|
||||
import {useSetActiveStarterPack} from '#/state/shell/starter-pack'
|
||||
import {Referrer, SharedPrefs} from '../../../modules/expo-bluesky-swiss-army'
|
||||
|
||||
export function useStarterPackEntry() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
import {httpStarterPackUriToAtUri} from 'lib/strings/starter-pack'
|
||||
import {useSetActiveStarterPack} from 'state/shell/starter-pack'
|
||||
import {httpStarterPackUriToAtUri} from '#/lib/strings/starter-pack'
|
||||
import {useSetActiveStarterPack} from '#/state/shell/starter-pack'
|
||||
|
||||
export function useStarterPackEntry() {
|
||||
const [ready, setReady] = React.useState(false)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const Beaker_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M13.5 4a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM10 5a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM8 6a1 1 0 0 0 0 2v2.64q-.319.374-.711.8l-.129.142c-.312.342-.649.711-.974 1.092-.731.857-1.488 1.866-1.89 2.99A4.9 4.9 0 0 0 4 17.298 4.7 4.7 0 0 0 8.702 22h6.596A4.7 4.7 0 0 0 20 17.298c0-.575-.114-1.122-.297-1.634-.401-1.124-1.157-2.133-1.89-2.99-.324-.38-.66-.75-.973-1.092h0l-.129-.141c-.26-.286-.5-.55-.711-.8V8a1 1 0 1 0 0-2H8Zm2 5.35V8h4v3.35l.22.275c.306.383.661.777 1.013 1.163l.13.143h0c.315.345.628.688.93 1.042.372.435.704.861.974 1.28l-.159.025c-.845.13-1.838.242-2.581.222-.842-.022-1.475-.217-2.227-.454l-.027-.008c-.746-.235-1.61-.507-2.746-.538-.743-.02-1.617.064-2.38.165q.26-.342.56-.692c.302-.354.615-.697.93-1.042l.13-.143c.352-.386.707-.78 1.014-1.163L10 11.35Zm7.41 5.905q.316-.048.586-.095.004.07.004.138A2.7 2.7 0 0 1 15.298 20H8.702A2.7 2.7 0 0 1 6 17.298q0-.213.039-.434c.236-.043.53-.093.853-.142.845-.13 1.837-.242 2.581-.222.842.022 1.475.217 2.227.454l.027.008c.746.235 1.61.507 2.746.538.931.024 2.07-.113 2.937-.245Z',
|
||||
})
|
||||
@@ -11,3 +11,7 @@ export const Bubble_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({
|
||||
path: 'M2.002 7a4 4 0 0 1 4-4h12a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H12.28l-4.762 2.858A1 1 0 0 1 6.002 21v-2a4 4 0 0 1-4-4V7Zm4-2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h1a1 1 0 0 1 1 1v1.234l3.486-2.092a1 1 0 0 1 .514-.142h6a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-12Z',
|
||||
})
|
||||
|
||||
export const Bubbles_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M6.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z',
|
||||
})
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const Car_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M7.018 6a1 1 0 0 0-.808.412L5.4 5.824l.809.588L3 10.825V17a1 1 0 1 0 2 0 1 1 0 0 1 1-1h12a1 1 0 0 1 1 1 1 1 0 1 0 2 0v-5.998l-3.22-4.577A1 1 0 0 0 16.962 6H7.018ZM23 11.686V17a3 3 0 0 1-5.83 1H6.83A3.001 3.001 0 0 1 1 17v-5.5a1 1 0 1 1 0-2h.49l3.102-4.265A3 3 0 0 1 7.018 4h9.944a3 3 0 0 1 2.453 1.274l3.104 4.412H23a1 1 0 1 1 0 2ZM5 13a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1Zm10 0a1 1 0 0 1 1-1h2a1 1 0 1 1 0 2h-2a1 1 0 0 1-1-1Z',
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import {createSinglePathSVG} from './TEMPLATE'
|
||||
|
||||
export const CircleQuestion_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Z" clip-rule="evenodd"/><path fill="#000" fill-rule="evenodd" d="M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z',
|
||||
path: 'M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Z M12 9a1 1 0 0 0-.879.522 1 1 0 0 1-1.754-.96A3 3 0 0 1 12 7c1.515 0 2.567 1.006 2.866 2.189.302 1.189-.156 2.574-1.524 3.258A.62.62 0 0 0 13 13a1 1 0 1 1-2 0c0-.992.56-1.898 1.447-2.342.455-.227.572-.618.48-.978C12.836 9.314 12.529 9 12 9Z M13 16a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z',
|
||||
})
|
||||
|
||||
@@ -3,3 +3,7 @@ import {createSinglePathSVG} from './TEMPLATE'
|
||||
export const CodeBrackets_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
||||
path: 'M14.242 3.03a1 1 0 0 1 .728 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414L3.414 12l3.293 3.293a1 1 0 1 1-1.414 1.414l-4-4a1 1 0 0 1 0-1.414l4-4a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1 0 1.414l-4 4a1 1 0 1 1-1.414-1.414L20.586 12l-3.293-3.293a1 1 0 0 1 0-1.414Z',
|
||||
})
|
||||
|
||||
export const CodeBrackets_Stroke2_Corner2_Rounded = createSinglePathSVG({
|
||||
path: 'M14.243 3.03a1 1 0 0 1 .727 1.213l-4 16a1 1 0 1 1-1.94-.485l4-16a1 1 0 0 1 1.213-.728ZM6.707 7.293a1 1 0 0 1 0 1.414l-2.586 2.586a1 1 0 0 0 0 1.414l2.586 2.586a1 1 0 1 1-1.414 1.414l-2.586-2.586a3 3 0 0 1 0-4.242l2.586-2.586a1 1 0 0 1 1.414 0Zm10.586 0a1 1 0 0 1 1.414 0l2.586 2.586a3 3 0 0 1 0 4.242l-2.586 2.586a1 1 0 1 1-1.414-1.414l2.586-2.586a1 1 0 0 0 0-1.414l-2.586-2.586a1 1 0 0 1 0-1.414Z',
|
||||
})
|
||||
|
||||