Merge branch 'main' into hailey/oauth
# Conflicts: # src/screens/Login/LoginForm.tsx
This commit is contained in:
+5
-13
@@ -23,20 +23,12 @@ module.exports = {
|
|||||||
'bsky-internal/avoid-unwrapped-text': [
|
'bsky-internal/avoid-unwrapped-text': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
impliedTextComponents: [
|
impliedTextComponents: ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P'],
|
||||||
'Button', // TODO: Not always safe.
|
|
||||||
'H1',
|
|
||||||
'H2',
|
|
||||||
'H3',
|
|
||||||
'H4',
|
|
||||||
'H5',
|
|
||||||
'H6',
|
|
||||||
'P',
|
|
||||||
'Prompt.Cancel', // TODO: Not always safe.
|
|
||||||
'Prompt.Action', // TODO: Not always safe.
|
|
||||||
'ToggleButton.Button', // TODO: Not always safe.
|
|
||||||
],
|
|
||||||
impliedTextProps: [],
|
impliedTextProps: [],
|
||||||
|
suggestedTextWrappers: {
|
||||||
|
Button: 'ButtonText',
|
||||||
|
'ToggleButton.Button': 'ToggleButton.ButtonText',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'simple-import-sort/imports': [
|
'simple-import-sort/imports': [
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ jobs:
|
|||||||
bundleDeploy:
|
bundleDeploy:
|
||||||
name: Bundle and Deploy EAS Update
|
name: Bundle and Deploy EAS Update
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-deploy
|
||||||
|
cancel-in-progress: true
|
||||||
outputs:
|
outputs:
|
||||||
fingerprint-is-different: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different }}
|
fingerprint-is-different: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different }}
|
||||||
|
|
||||||
@@ -44,7 +47,18 @@ jobs:
|
|||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 100
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: ⬇️ Get last successful deployment commit from the cache
|
||||||
|
id: get-base-commit
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: last-successful-commit-hash.txt
|
||||||
|
key: last-successful-deployment-commit-${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Add the last successful deployment commit to the output
|
||||||
|
id: last-successful-commit
|
||||||
|
run: echo base-commit=$(cat last-successful-commit-hash.txt) >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: ⬇️ Fetch commits from base branch
|
- name: ⬇️ Fetch commits from base branch
|
||||||
if: ${{ github.ref != 'refs/heads/main' }}
|
if: ${{ github.ref != 'refs/heads/main' }}
|
||||||
@@ -57,12 +71,12 @@ jobs:
|
|||||||
if [ -z "${{ inputs.channel == 'production' }}" ]; then
|
if [ -z "${{ inputs.channel == 'production' }}" ]; then
|
||||||
echo base-commit=$(git show-ref -s ${{ inputs.runtimeVersion }}) >> "$GITHUB_OUTPUT"
|
echo base-commit=$(git show-ref -s ${{ inputs.runtimeVersion }}) >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo base-commit=$(git log -n 1 --skip 1 main --pretty=format:'%H') >> "$GITHUB_OUTPUT"
|
echo base-commit=${{ steps.last-successful-commit.base-commit }} >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: ✓ Make sure we found a base commit
|
- name: ✓ Make sure we found a base commit
|
||||||
run: |
|
run: |
|
||||||
if [ -z "${{ steps.base-commit.outputs.base-commit }}" ]; then
|
if [ -z "${{ steps.base-commit.outputs.base-commit }}" && ${{ inputs.channel == 'production' }} ]; then
|
||||||
echo "Could not find a base commit for this release. Exiting."
|
echo "Could not find a base commit for this release. Exiting."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -82,7 +96,6 @@ jobs:
|
|||||||
uses: expo/expo-github-action/fingerprint@main
|
uses: expo/expo-github-action/fingerprint@main
|
||||||
with:
|
with:
|
||||||
previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
|
previous-git-commit: ${{ steps.base-commit.outputs.base-commit }}
|
||||||
args:
|
|
||||||
|
|
||||||
- name: 👀 Debug fingerprint
|
- name: 👀 Debug fingerprint
|
||||||
id: fingerprint-debug
|
id: fingerprint-debug
|
||||||
@@ -90,13 +103,26 @@ jobs:
|
|||||||
echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}"
|
echo "previousGitCommit=${{ steps.fingerprint.outputs.previous-git-commit }} currentGitCommit=${{ steps.fingerprint.outputs.current-git-commit }}"
|
||||||
echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"
|
echo "isPreviousFingerprintEmpty=${{ steps.fingerprint.outputs.previous-fingerprint == '' }}"
|
||||||
|
|
||||||
fingerprintDiff="${{ steps.fingerprint.outputs.fingerprint-diff }}"
|
fingerprintDiff='$(echo "${{ steps.fingerprint.outputs.fingerprint-diff }}")'
|
||||||
|
|
||||||
if [[ $fingerprintDiff =~ "bareRncliAutolinking" || $fingerprintDiff =~ "expoAutolinkingAndroid" || $fingerprintDiff =~ "expoAutolinkingIos" ]]; then
|
if [[ $fingerprintDiff =~ "bareRncliAutolinking" || $fingerprintDiff =~ "expoAutolinkingAndroid" || $fingerprintDiff =~ "expoAutolinkingIos" ]]; then
|
||||||
echo fingerprint-is-different="true" >> "$GITHUB_OUTPUT"
|
echo fingerprint-is-different="true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo fingerprint-is-different="false" >> "$GITHUB_OUTPUT"
|
echo fingerprint-is-different="false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Lint check
|
||||||
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: Prettier check
|
||||||
|
run: yarn prettier --check .
|
||||||
|
|
||||||
|
- name: Check & compile i18n
|
||||||
|
run: yarn intl:build
|
||||||
|
|
||||||
|
- name: Type check
|
||||||
|
run: yarn typecheck
|
||||||
|
|
||||||
- name: 🔨 Setup EAS
|
- name: 🔨 Setup EAS
|
||||||
uses: expo/expo-github-action@v8
|
uses: expo/expo-github-action@v8
|
||||||
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
||||||
@@ -113,10 +139,6 @@ jobs:
|
|||||||
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
||||||
uses: dcarbone/install-jq-action@v2
|
uses: dcarbone/install-jq-action@v2
|
||||||
|
|
||||||
- name: 🔤 Compile Translations
|
|
||||||
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
|
||||||
run: yarn intl:build
|
|
||||||
|
|
||||||
- name: ✏️ Write environment variables
|
- name: ✏️ Write environment variables
|
||||||
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
if: ${{ steps.fingerprint-debug.outputs.fingerprint-is-different == 'false'}}
|
||||||
run: |
|
run: |
|
||||||
@@ -136,11 +158,16 @@ jobs:
|
|||||||
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
|
RUNTIME_VERSION: ${{ inputs.runtimeVersion }}
|
||||||
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
|
CHANNEL_NAME: ${{ inputs.channel || 'testflight' }}
|
||||||
|
|
||||||
|
- name: Save successful deployment commit hash
|
||||||
|
run: echo ${{ steps.fingerprint.outputs.current-git-commit }} > last-successful-commit-hash.txt
|
||||||
|
|
||||||
# GitHub actions are horrible so let's just copy paste this in
|
# GitHub actions are horrible so let's just copy paste this in
|
||||||
buildIfNecessaryIOS:
|
buildIfNecessaryIOS:
|
||||||
name: Build and Submit iOS
|
name: Build and Submit iOS
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-ios
|
||||||
|
cancel-in-progress: false
|
||||||
needs: [bundleDeploy]
|
needs: [bundleDeploy]
|
||||||
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
||||||
# available here
|
# available here
|
||||||
@@ -206,6 +233,9 @@ jobs:
|
|||||||
buildIfNecessaryAndroid:
|
buildIfNecessaryAndroid:
|
||||||
name: Build and Submit Android
|
name: Build and Submit Android
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}-build-android
|
||||||
|
cancel-in-progress: false
|
||||||
needs: [ bundleDeploy ]
|
needs: [ bundleDeploy ]
|
||||||
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
# Gotta check if its NOT '[]' because any md5 hash in the outputs is detected as a possible secret and won't be
|
||||||
# available here
|
# available here
|
||||||
|
|||||||
@@ -13,13 +13,15 @@ concurrency:
|
|||||||
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
|
||||||
webpack-analyzer:
|
|
||||||
runs-on: ubuntu-22.04
|
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
actions: write
|
actions: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
webpack-analyzer:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
steps:
|
steps:
|
||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -94,11 +96,8 @@ jobs:
|
|||||||
|
|
||||||
test-suite-fingerprint:
|
test-suite-fingerprint:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
if: ${{ github.event_name == 'pull_request' || github.event_name == 'push' }}
|
if: ${{ github.event.pull_request.head.repo.full_name == github.repository || github.event_name == 'push' }}
|
||||||
concurrency: fingerprint-${{ github.event_name != 'pull_request' && 'main' || github.run_id }}
|
concurrency: fingerprint-${{ github.event_name != 'pull_request' && 'main' || github.run_id }}
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
actions: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: ⬇️ Checkout
|
- name: ⬇️ Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ describe('avoid-unwrapped-text', () => {
|
|||||||
|
|
||||||
{
|
{
|
||||||
code: `
|
code: `
|
||||||
<View prop={
|
<View propText={
|
||||||
<Trans><Text>foo</Text></Trans>
|
<Trans><Text>foo</Text></Trans>
|
||||||
}>
|
}>
|
||||||
<Bar />
|
<Bar />
|
||||||
@@ -281,6 +281,170 @@ function MyText({ foo }) {
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text>{'foo'}</Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text>{foo + 'foo'}</Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text><Trans>{'foo'}</Trans></Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo['bar'] && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{(foo === 'bar') && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{(foo !== 'bar') && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text>{\`foo\`}</Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text><Trans>{\`foo\`}</Trans></Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text>{_(msg\`foo\`)}</Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Text><Trans>{_(msg\`foo\`)}</Trans></Text>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<Foo>
|
||||||
|
<View prop={stuff('foo')}>
|
||||||
|
<Bar />
|
||||||
|
</View>
|
||||||
|
</Foo>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<Foo>
|
||||||
|
<View onClick={() => stuff('foo')}>
|
||||||
|
<Bar />
|
||||||
|
</View>
|
||||||
|
</Foo>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{renderItem('foo')}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo === 'foo' && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo['foo'] && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{check('foo') && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo.bar && <Bar />}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<Text>
|
||||||
|
<Trans>{renderItem('foo')}</Trans>
|
||||||
|
</Text>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{null}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<Text>
|
||||||
|
<Trans>{null}</Trans>
|
||||||
|
</Text>
|
||||||
|
`,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
invalid: [
|
invalid: [
|
||||||
@@ -455,6 +619,179 @@ function MyText({ foo }) {
|
|||||||
`,
|
`,
|
||||||
errors: 1,
|
errors: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{'foo'}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo && 'foo'}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{'foo'}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo && <Trans>{'foo'}</Trans>}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{10}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{10}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{foo + 10}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{\`foo\`}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{\`foo\`}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{foo + \`foo\`}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{_(msg\`foo\`)}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
{foo + _(msg\`foo\`)}
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{_(msg\`foo\`)}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{foo + _(msg\`foo\`)}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>foo</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans><Trans>foo</Trans></Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{foo}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View>
|
||||||
|
<Trans>{'foo'}</Trans>
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
code: `
|
||||||
|
<View prop={
|
||||||
|
<Trans><Text>foo</Text></Trans>
|
||||||
|
}>
|
||||||
|
<Bar />
|
||||||
|
</View>
|
||||||
|
`,
|
||||||
|
errors: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ exports.create = function create(context) {
|
|||||||
const options = context.options[0] || {}
|
const options = context.options[0] || {}
|
||||||
const impliedTextProps = options.impliedTextProps ?? []
|
const impliedTextProps = options.impliedTextProps ?? []
|
||||||
const impliedTextComponents = options.impliedTextComponents ?? []
|
const impliedTextComponents = options.impliedTextComponents ?? []
|
||||||
|
const suggestedTextWrappers = options.suggestedTextWrappers ?? {}
|
||||||
const textProps = [...impliedTextProps]
|
const textProps = [...impliedTextProps]
|
||||||
const textComponents = ['Text', ...impliedTextComponents]
|
const textComponents = ['Text', ...impliedTextComponents]
|
||||||
|
|
||||||
@@ -54,13 +55,13 @@ exports.create = function create(context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (tagName === 'Trans') {
|
if (tagName === 'Trans') {
|
||||||
// Skip over it and check above.
|
// Exit and rely on the traversal for <Trans> JSXElement (code below).
|
||||||
// TODO: Maybe validate that it's present.
|
// TODO: Maybe validate that it's present.
|
||||||
parent = parent.parent
|
return
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
let message = 'Wrap this string in <Text>.'
|
const suggestedWrapper = suggestedTextWrappers[tagName]
|
||||||
if (tagName !== 'View') {
|
let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.`
|
||||||
|
if (tagName !== 'View' && !suggestedWrapper) {
|
||||||
message +=
|
message +=
|
||||||
' If <' +
|
' If <' +
|
||||||
tagName +
|
tagName +
|
||||||
@@ -112,6 +113,189 @@ exports.create = function create(context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Literal(node) {
|
||||||
|
if (typeof node.value !== 'string' && typeof node.value !== 'number') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let parent = node.parent
|
||||||
|
while (parent) {
|
||||||
|
if (parent.type === 'JSXElement') {
|
||||||
|
const tagName = getTagName(parent)
|
||||||
|
if (isTextComponent(tagName)) {
|
||||||
|
// We're good.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tagName === 'Trans') {
|
||||||
|
// Exit and rely on the traversal for <Trans> JSXElement (code below).
|
||||||
|
// TODO: Maybe validate that it's present.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const suggestedWrapper = suggestedTextWrappers[tagName]
|
||||||
|
let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.`
|
||||||
|
if (tagName !== 'View' && !suggestedWrapper) {
|
||||||
|
message +=
|
||||||
|
' If <' +
|
||||||
|
tagName +
|
||||||
|
'> is guaranteed to render <Text>, ' +
|
||||||
|
'rename it to <' +
|
||||||
|
tagName +
|
||||||
|
'Text> or add it to impliedTextComponents.'
|
||||||
|
}
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.type === 'BinaryExpression' && parent.operator === '+') {
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parent.type === 'JSXExpressionContainer' ||
|
||||||
|
parent.type === 'LogicalExpression'
|
||||||
|
) {
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be conservative for other types.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TemplateLiteral(node) {
|
||||||
|
let parent = node.parent
|
||||||
|
while (parent) {
|
||||||
|
if (parent.type === 'JSXElement') {
|
||||||
|
const tagName = getTagName(parent)
|
||||||
|
if (isTextComponent(tagName)) {
|
||||||
|
// We're good.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tagName === 'Trans') {
|
||||||
|
// Exit and rely on the traversal for <Trans> JSXElement (code below).
|
||||||
|
// TODO: Maybe validate that it's present.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const suggestedWrapper = suggestedTextWrappers[tagName]
|
||||||
|
let message = `Wrap this string in <${suggestedWrapper ?? 'Text'}>.`
|
||||||
|
if (tagName !== 'View' && !suggestedWrapper) {
|
||||||
|
message +=
|
||||||
|
' If <' +
|
||||||
|
tagName +
|
||||||
|
'> is guaranteed to render <Text>, ' +
|
||||||
|
'rename it to <' +
|
||||||
|
tagName +
|
||||||
|
'Text> or add it to impliedTextComponents.'
|
||||||
|
}
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parent.type === 'CallExpression' &&
|
||||||
|
parent.callee.type === 'Identifier' &&
|
||||||
|
parent.callee.name === '_'
|
||||||
|
) {
|
||||||
|
// This is a user-facing string, keep going up.
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.type === 'BinaryExpression' && parent.operator === '+') {
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parent.type === 'JSXExpressionContainer' ||
|
||||||
|
parent.type === 'LogicalExpression' ||
|
||||||
|
parent.type === 'TaggedTemplateExpression'
|
||||||
|
) {
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Be conservative for other types.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
JSXElement(node) {
|
||||||
|
if (getTagName(node) !== 'Trans') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let parent = node.parent
|
||||||
|
while (parent) {
|
||||||
|
if (parent.type === 'JSXElement') {
|
||||||
|
const tagName = getTagName(parent)
|
||||||
|
if (isTextComponent(tagName)) {
|
||||||
|
// We're good.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (tagName === 'Trans') {
|
||||||
|
// Exit and rely on the traversal for this JSXElement.
|
||||||
|
// TODO: Should nested <Trans> even be allowed?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const suggestedWrapper = suggestedTextWrappers[tagName]
|
||||||
|
let message = `Wrap this <Trans> in <${suggestedWrapper ?? 'Text'}>.`
|
||||||
|
if (tagName !== 'View' && !suggestedWrapper) {
|
||||||
|
message +=
|
||||||
|
' If <' +
|
||||||
|
tagName +
|
||||||
|
'> is guaranteed to render <Text>, ' +
|
||||||
|
'rename it to <' +
|
||||||
|
tagName +
|
||||||
|
'Text> or add it to impliedTextComponents.'
|
||||||
|
}
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
parent.type === 'JSXAttribute' &&
|
||||||
|
parent.name.type === 'JSXIdentifier' &&
|
||||||
|
parent.parent.type === 'JSXOpeningElement' &&
|
||||||
|
parent.parent.parent.type === 'JSXElement'
|
||||||
|
) {
|
||||||
|
const tagName = getTagName(parent.parent.parent)
|
||||||
|
const propName = parent.name.name
|
||||||
|
if (
|
||||||
|
textProps.includes(tagName + ' ' + propName) ||
|
||||||
|
propName === 'text' ||
|
||||||
|
propName.endsWith('Text')
|
||||||
|
) {
|
||||||
|
// We're good.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const message =
|
||||||
|
'Wrap this <Trans> in <Text>.' +
|
||||||
|
' If `' +
|
||||||
|
propName +
|
||||||
|
'` is guaranteed to be wrapped in <Text>, ' +
|
||||||
|
'rename it to `' +
|
||||||
|
propName +
|
||||||
|
'Text' +
|
||||||
|
'` or add it to impliedTextProps.'
|
||||||
|
context.report({
|
||||||
|
node,
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = parent.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
},
|
||||||
ReturnStatement(node) {
|
ReturnStatement(node) {
|
||||||
let fnScope = context.getScope()
|
let fnScope = context.getScope()
|
||||||
while (fnScope && fnScope.type !== 'function') {
|
while (fnScope && fnScope.type !== 'function') {
|
||||||
|
|||||||
+2
-2
@@ -35,7 +35,7 @@
|
|||||||
"e2e:run": "NODE_ENV=test detox test --configuration ios.sim.debug --take-screenshots all",
|
"e2e:run": "NODE_ENV=test detox test --configuration ios.sim.debug --take-screenshots all",
|
||||||
"perf:test": "NODE_ENV=test maestro test",
|
"perf:test": "NODE_ENV=test maestro test",
|
||||||
"perf:test:run": "NODE_ENV=test maestro test __e2e__/maestro/scroll.yaml",
|
"perf:test:run": "NODE_ENV=test maestro test __e2e__/maestro/scroll.yaml",
|
||||||
"perf:test:measure": "NODE_ENV=test flashlight test --bundleId xyz.blueskyweb.app --testCommand 'yarn perf:test' --duration 150000 --resultsFilePath .perf/results.json",
|
"perf:test:measure": "NODE_ENV=test flashlight test --bundleId xyz.blueskyweb.app --testCommand \"yarn perf:test\" --duration 150000 --resultsFilePath .perf/results.json",
|
||||||
"perf:test:results": "NODE_ENV=test flashlight report .perf/results.json",
|
"perf:test:results": "NODE_ENV=test flashlight report .perf/results.json",
|
||||||
"perf:measure": "NODE_ENV=test flashlight measure",
|
"perf:measure": "NODE_ENV=test flashlight measure",
|
||||||
"intl:build": "yarn intl:extract && yarn intl:compile",
|
"intl:build": "yarn intl:extract && yarn intl:compile",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"@mattermost/react-native-paste-input": "^0.6.4",
|
"@mattermost/react-native-paste-input": "^0.6.4",
|
||||||
"@miblanchard/react-native-slider": "^2.3.1",
|
"@miblanchard/react-native-slider": "^2.3.1",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@react-native-async-storage/async-storage": "1.21.0",
|
"@react-native-async-storage/async-storage": "1.23.1",
|
||||||
"@react-native-masked-view/masked-view": "0.3.0",
|
"@react-native-masked-view/masked-view": "0.3.0",
|
||||||
"@react-native-menu/menu": "^0.8.0",
|
"@react-native-menu/menu": "^0.8.0",
|
||||||
"@react-native-picker/picker": "2.6.1",
|
"@react-native-picker/picker": "2.6.1",
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import {
|
|||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {LinearGradient} from 'expo-linear-gradient'
|
import {LinearGradient} from 'expo-linear-gradient'
|
||||||
import {Trans} from '@lingui/macro'
|
|
||||||
|
|
||||||
import {android, atoms as a, flatten, tokens, useTheme} from '#/alf'
|
import {android, atoms as a, flatten, tokens, useTheme} from '#/alf'
|
||||||
import {Props as SVGIconProps} from '#/components/icons/common'
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
||||||
@@ -59,6 +58,10 @@ export type ButtonState = {
|
|||||||
|
|
||||||
export type ButtonContext = VariantProps & ButtonState
|
export type ButtonContext = VariantProps & ButtonState
|
||||||
|
|
||||||
|
type NonTextElements =
|
||||||
|
| React.ReactElement
|
||||||
|
| Iterable<React.ReactElement | null | undefined | boolean>
|
||||||
|
|
||||||
export type ButtonProps = Pick<
|
export type ButtonProps = Pick<
|
||||||
PressableProps,
|
PressableProps,
|
||||||
'disabled' | 'onPress' | 'testID'
|
'disabled' | 'onPress' | 'testID'
|
||||||
@@ -68,11 +71,9 @@ export type ButtonProps = Pick<
|
|||||||
testID?: string
|
testID?: string
|
||||||
label: string
|
label: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
children:
|
children: NonTextElements | ((context: ButtonContext) => NonTextElements)
|
||||||
| React.ReactNode
|
|
||||||
| string
|
|
||||||
| ((context: ButtonContext) => React.ReactNode | string)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
|
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
|
||||||
|
|
||||||
const Context = React.createContext<VariantProps & ButtonState>({
|
const Context = React.createContext<VariantProps & ButtonState>({
|
||||||
@@ -404,15 +405,7 @@ export function Button({
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
<Context.Provider value={context}>
|
<Context.Provider value={context}>
|
||||||
{/* @ts-ignore */}
|
{typeof children === 'function' ? children(context) : children}
|
||||||
{typeof children === 'string' || children?.type === Trans ? (
|
|
||||||
/* @ts-ignore */
|
|
||||||
<ButtonText>{children}</ButtonText>
|
|
||||||
) : typeof children === 'function' ? (
|
|
||||||
children(context)
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export function useDialogControl(): DialogOuterProps['control'] {
|
|||||||
control.current.open()
|
control.current.open()
|
||||||
},
|
},
|
||||||
close: cb => {
|
close: cb => {
|
||||||
control.current.close(cb)
|
control.current.close()
|
||||||
|
cb?.()
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[id, control],
|
[id, control],
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import React, {useImperativeHandle} from 'react'
|
import React, {useImperativeHandle} from 'react'
|
||||||
import {View, TouchableWithoutFeedback} from 'react-native'
|
import {TouchableWithoutFeedback, View} from 'react-native'
|
||||||
import {FocusScope} from '@tamagui/focus-scope'
|
import Animated, {FadeIn, FadeInDown} from 'react-native-reanimated'
|
||||||
import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {FocusScope} from '@tamagui/focus-scope'
|
||||||
|
|
||||||
import {useTheme, atoms as a, useBreakpoints, web, flatten} from '#/alf'
|
import {logger} from '#/logger'
|
||||||
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
|
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
|
||||||
|
import {Button, ButtonIcon} from '#/components/Button'
|
||||||
|
import {Context} from '#/components/Dialog/context'
|
||||||
|
import {
|
||||||
|
DialogControlProps,
|
||||||
|
DialogInnerProps,
|
||||||
|
DialogOuterProps,
|
||||||
|
} from '#/components/Dialog/types'
|
||||||
|
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
||||||
import {Portal} from '#/components/Portal'
|
import {Portal} from '#/components/Portal'
|
||||||
|
|
||||||
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
|
export {useDialogContext, useDialogControl} from '#/components/Dialog/context'
|
||||||
import {Context} from '#/components/Dialog/context'
|
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
|
||||||
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
|
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
|
||||||
|
|
||||||
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
|
|
||||||
export * from '#/components/Dialog/types'
|
export * from '#/components/Dialog/types'
|
||||||
export {Input} from '#/components/forms/TextField'
|
export {Input} from '#/components/forms/TextField'
|
||||||
|
|
||||||
@@ -37,14 +41,31 @@ export function Outer({
|
|||||||
setDialogIsOpen(control.id, true)
|
setDialogIsOpen(control.id, true)
|
||||||
}, [setIsOpen, setDialogIsOpen, control.id])
|
}, [setIsOpen, setDialogIsOpen, control.id])
|
||||||
|
|
||||||
const close = React.useCallback(async () => {
|
const onCloseInner = React.useCallback(async () => {
|
||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
await new Promise(resolve => setTimeout(resolve, 150))
|
await new Promise(resolve => setTimeout(resolve, 150))
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
setIsVisible(true)
|
setIsVisible(true)
|
||||||
setDialogIsOpen(control.id, false)
|
setDialogIsOpen(control.id, false)
|
||||||
onClose?.()
|
onClose?.()
|
||||||
}, [onClose, setIsOpen, setDialogIsOpen, control.id])
|
}, [control.id, onClose, setDialogIsOpen])
|
||||||
|
|
||||||
|
const close = React.useCallback<DialogControlProps['close']>(
|
||||||
|
cb => {
|
||||||
|
try {
|
||||||
|
if (cb && typeof cb === 'function') {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`Dialog closeCallback failed`, {
|
||||||
|
message: e.message,
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
onCloseInner()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onCloseInner],
|
||||||
|
)
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
control.ref,
|
control.ref,
|
||||||
@@ -52,7 +73,7 @@ export function Outer({
|
|||||||
open,
|
open,
|
||||||
close,
|
close,
|
||||||
}),
|
}),
|
||||||
[open, close],
|
[close, open],
|
||||||
)
|
)
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -65,7 +86,7 @@ export function Outer({
|
|||||||
document.addEventListener('keydown', handler)
|
document.addEventListener('keydown', handler)
|
||||||
|
|
||||||
return () => document.removeEventListener('keydown', handler)
|
return () => document.removeEventListener('keydown', handler)
|
||||||
}, [isOpen, close])
|
}, [close, isOpen])
|
||||||
|
|
||||||
const context = React.useMemo(
|
const context = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@@ -82,7 +103,7 @@ export function Outer({
|
|||||||
<TouchableWithoutFeedback
|
<TouchableWithoutFeedback
|
||||||
accessibilityHint={undefined}
|
accessibilityHint={undefined}
|
||||||
accessibilityLabel={_(msg`Close active dialog`)}
|
accessibilityLabel={_(msg`Close active dialog`)}
|
||||||
onPress={close}>
|
onPress={onCloseInner}>
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
web(a.fixed),
|
web(a.fixed),
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {useLingui} from '@lingui/react'
|
|||||||
import {cleanError} from 'lib/strings/errors'
|
import {cleanError} from 'lib/strings/errors'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {Error} from '#/components/Error'
|
import {Error} from '#/components/Error'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
@@ -87,7 +87,9 @@ function ListFooterMaybeError({
|
|||||||
a.py_sm,
|
a.py_sm,
|
||||||
]}
|
]}
|
||||||
onPress={onRetry}>
|
onPress={onRetry}>
|
||||||
|
<ButtonText>
|
||||||
<Trans>Retry</Trans>
|
<Trans>Retry</Trans>
|
||||||
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -91,15 +91,13 @@ export function Actions({children}: React.PropsWithChildren<{}>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Cancel({
|
export function Cancel({
|
||||||
children,
|
|
||||||
cta,
|
cta,
|
||||||
}: React.PropsWithChildren<{
|
}: {
|
||||||
/**
|
/**
|
||||||
* Optional i18n string, used in lieu of `children` for simple buttons. If
|
* Optional i18n string. If undefined, it will default to "Cancel".
|
||||||
* undefined (and `children` is undefined), it will default to "Cancel".
|
|
||||||
*/
|
*/
|
||||||
cta?: string
|
cta?: string
|
||||||
}>) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {close} = Dialog.useDialogContext()
|
const {close} = Dialog.useDialogContext()
|
||||||
@@ -114,33 +112,30 @@ export function Cancel({
|
|||||||
size={gtMobile ? 'small' : 'medium'}
|
size={gtMobile ? 'small' : 'medium'}
|
||||||
label={cta || _(msg`Cancel`)}
|
label={cta || _(msg`Cancel`)}
|
||||||
onPress={onPress}>
|
onPress={onPress}>
|
||||||
{children ? children : <ButtonText>{cta || _(msg`Cancel`)}</ButtonText>}
|
<ButtonText>{cta || _(msg`Cancel`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Action({
|
export function Action({
|
||||||
children,
|
|
||||||
onPress,
|
onPress,
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
cta,
|
cta,
|
||||||
testID,
|
testID,
|
||||||
}: React.PropsWithChildren<{
|
}: {
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
color?: ButtonColor
|
color?: ButtonColor
|
||||||
/**
|
/**
|
||||||
* Optional i18n string, used in lieu of `children` for simple buttons. If
|
* Optional i18n string. If undefined, it will default to "Confirm".
|
||||||
* undefined (and `children` is undefined), it will default to "Confirm".
|
|
||||||
*/
|
*/
|
||||||
cta?: string
|
cta?: string
|
||||||
testID?: string
|
testID?: string
|
||||||
}>) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {close} = Dialog.useDialogContext()
|
const {close} = Dialog.useDialogContext()
|
||||||
const handleOnPress = React.useCallback(() => {
|
const handleOnPress = React.useCallback(() => {
|
||||||
close()
|
close(onPress)
|
||||||
onPress()
|
|
||||||
}, [close, onPress])
|
}, [close, onPress])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -151,7 +146,7 @@ export function Action({
|
|||||||
label={cta || _(msg`Confirm`)}
|
label={cta || _(msg`Confirm`)}
|
||||||
onPress={handleOnPress}
|
onPress={handleOnPress}
|
||||||
testID={testID}>
|
testID={testID}>
|
||||||
{children ? children : <ButtonText>{cta || _(msg`Confirm`)}</ButtonText>}
|
<ButtonText>{cta || _(msg`Confirm`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View, AccessibilityProps, TextStyle, ViewStyle} from 'react-native'
|
import {AccessibilityProps, TextStyle, View, ViewStyle} from 'react-native'
|
||||||
|
|
||||||
import {atoms as a, useTheme, native} from '#/alf'
|
import {atoms as a, native, useTheme} from '#/alf'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
import {Text} from '#/components/Typography'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> &
|
||||||
|
AccessibilityProps & {
|
||||||
export type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> &
|
children: React.ReactElement
|
||||||
AccessibilityProps &
|
|
||||||
React.PropsWithChildren<{
|
|
||||||
testID?: string
|
testID?: string
|
||||||
}>
|
}
|
||||||
|
|
||||||
export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & {
|
export type GroupProps = Omit<Toggle.GroupProps, 'style' | 'type'> & {
|
||||||
multiple?: boolean
|
multiple?: boolean
|
||||||
@@ -47,12 +46,10 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const state = Toggle.useItemContext()
|
const state = Toggle.useItemContext()
|
||||||
|
|
||||||
const {baseStyles, hoverStyles, activeStyles, textStyles} =
|
const {baseStyles, hoverStyles, activeStyles} = React.useMemo(() => {
|
||||||
React.useMemo(() => {
|
|
||||||
const base: ViewStyle[] = []
|
const base: ViewStyle[] = []
|
||||||
const hover: ViewStyle[] = []
|
const hover: ViewStyle[] = []
|
||||||
const active: ViewStyle[] = []
|
const active: ViewStyle[] = []
|
||||||
const text: TextStyle[] = []
|
|
||||||
|
|
||||||
hover.push(
|
hover.push(
|
||||||
t.name === 'light' ? t.atoms.bg_contrast_100 : t.atoms.bg_contrast_25,
|
t.name === 'light' ? t.atoms.bg_contrast_100 : t.atoms.bg_contrast_25,
|
||||||
@@ -62,7 +59,6 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
|||||||
active.push({
|
active.push({
|
||||||
backgroundColor: t.palette.contrast_800,
|
backgroundColor: t.palette.contrast_800,
|
||||||
})
|
})
|
||||||
text.push(t.atoms.text_inverted)
|
|
||||||
hover.push({
|
hover.push({
|
||||||
backgroundColor: t.palette.contrast_800,
|
backgroundColor: t.palette.contrast_800,
|
||||||
})
|
})
|
||||||
@@ -78,16 +74,12 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
|||||||
base.push({
|
base.push({
|
||||||
backgroundColor: t.palette.contrast_100,
|
backgroundColor: t.palette.contrast_100,
|
||||||
})
|
})
|
||||||
text.push({
|
|
||||||
opacity: 0.5,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
baseStyles: base,
|
baseStyles: base,
|
||||||
hoverStyles: hover,
|
hoverStyles: hover,
|
||||||
activeStyles: active,
|
activeStyles: active,
|
||||||
textStyles: text,
|
|
||||||
}
|
}
|
||||||
}, [t, state])
|
}, [t, state])
|
||||||
|
|
||||||
@@ -110,7 +102,29 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
|||||||
activeStyles,
|
activeStyles,
|
||||||
(state.hovered || state.pressed) && hoverStyles,
|
(state.hovered || state.pressed) && hoverStyles,
|
||||||
]}>
|
]}>
|
||||||
{typeof children === 'string' ? (
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ButtonText({children}: {children: React.ReactNode}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const state = Toggle.useItemContext()
|
||||||
|
|
||||||
|
const textStyles = React.useMemo(() => {
|
||||||
|
const text: TextStyle[] = []
|
||||||
|
if (state.selected) {
|
||||||
|
text.push(t.atoms.text_inverted)
|
||||||
|
}
|
||||||
|
if (state.disabled) {
|
||||||
|
text.push({
|
||||||
|
opacity: 0.5,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}, [t, state])
|
||||||
|
|
||||||
|
return (
|
||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.text_center,
|
a.text_center,
|
||||||
@@ -120,9 +134,5 @@ function ButtonInner({children}: React.PropsWithChildren<{}>) {
|
|||||||
]}>
|
]}>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,17 +84,17 @@ export function Buttons({
|
|||||||
onChange={onChange}>
|
onChange={onChange}>
|
||||||
{ignoreLabel && (
|
{ignoreLabel && (
|
||||||
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
<ToggleButton.Button name="ignore" label={ignoreLabel}>
|
||||||
{ignoreLabel}
|
<ToggleButton.ButtonText>{ignoreLabel}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
)}
|
)}
|
||||||
{warnLabel && (
|
{warnLabel && (
|
||||||
<ToggleButton.Button name="warn" label={warnLabel}>
|
<ToggleButton.Button name="warn" label={warnLabel}>
|
||||||
{warnLabel}
|
<ToggleButton.ButtonText>{warnLabel}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
)}
|
)}
|
||||||
{hideLabel && (
|
{hideLabel && (
|
||||||
<ToggleButton.Button name="hide" label={hideLabel}>
|
<ToggleButton.Button name="hide" label={hideLabel}>
|
||||||
{hideLabel}
|
<ToggleButton.ButtonText>{hideLabel}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
)}
|
)}
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ function AppealForm({
|
|||||||
size="medium"
|
size="medium"
|
||||||
onPress={onPressBack}
|
onPress={onPressBack}
|
||||||
label={_(msg`Back`)}>
|
label={_(msg`Back`)}>
|
||||||
{_(msg`Back`)}
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
testID="submitBtn"
|
testID="submitBtn"
|
||||||
@@ -253,7 +253,7 @@ function AppealForm({
|
|||||||
size="medium"
|
size="medium"
|
||||||
onPress={onSubmit}
|
onPress={onSubmit}
|
||||||
label={_(msg`Submit`)}>
|
label={_(msg`Submit`)}>
|
||||||
{_(msg`Submit`)}
|
<ButtonText>{_(msg`Submit`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -45,10 +45,12 @@ export type LogEvents = {
|
|||||||
'onboarding:moderation:nextPressed': {}
|
'onboarding:moderation:nextPressed': {}
|
||||||
'onboarding:finished:nextPressed': {}
|
'onboarding:finished:nextPressed': {}
|
||||||
'feed:endReached': {
|
'feed:endReached': {
|
||||||
|
feedUrl: string
|
||||||
feedType: string
|
feedType: string
|
||||||
itemCount: number
|
itemCount: number
|
||||||
}
|
}
|
||||||
'feed:refresh': {
|
'feed:refresh': {
|
||||||
|
feedUrl: string
|
||||||
feedType: string
|
feedType: string
|
||||||
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
|
reason: 'pull-to-refresh' | 'soft-reset' | 'load-latest'
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,10 @@ export type LogEvents = {
|
|||||||
logContext: 'Composer'
|
logContext: 'Composer'
|
||||||
}
|
}
|
||||||
'post:like': {
|
'post:like': {
|
||||||
|
doesLikerFollowPoster: boolean | undefined
|
||||||
|
doesPosterFollowLiker: boolean | undefined
|
||||||
|
likerClout: number | undefined
|
||||||
|
postClout: number | undefined
|
||||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
}
|
}
|
||||||
'post:repost': {
|
'post:repost': {
|
||||||
@@ -77,6 +83,9 @@ export type LogEvents = {
|
|||||||
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
logContext: 'FeedItem' | 'PostThreadItem' | 'Post'
|
||||||
}
|
}
|
||||||
'profile:follow': {
|
'profile:follow': {
|
||||||
|
didBecomeMutual: boolean | undefined
|
||||||
|
followeeClout: number | undefined
|
||||||
|
followerClout: number | undefined
|
||||||
logContext:
|
logContext:
|
||||||
| 'RecommendedFollowsItem'
|
| 'RecommendedFollowsItem'
|
||||||
| 'PostThreadItem'
|
| 'PostThreadItem'
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ export function attachRouteToLogEvents(
|
|||||||
getCurrentRouteName = getRouteName
|
getCurrentRouteName = getRouteName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toClout(n: number | null | undefined): number | undefined {
|
||||||
|
if (n == null) {
|
||||||
|
return undefined
|
||||||
|
} else {
|
||||||
|
return Math.max(0, Math.round(Math.log(n)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function logEvent<E extends keyof LogEvents>(
|
export function logEvent<E extends keyof LogEvents>(
|
||||||
eventName: E & string,
|
eventName: E & string,
|
||||||
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
rawMetadata: LogEvents[E] & FlatJSONRecord,
|
||||||
@@ -78,6 +86,10 @@ function toStatsigUser(did: string | undefined) {
|
|||||||
return {
|
return {
|
||||||
userID,
|
userID,
|
||||||
platform: Platform.OS,
|
platform: Platform.OS,
|
||||||
|
custom: {
|
||||||
|
// Need to specify here too for gating.
|
||||||
|
platform: Platform.OS,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {useLoggedOutViewControls} from '#/state/shell/logged-out'
|
|||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {AccountList} from '#/components/AccountList'
|
import {AccountList} from '#/components/AccountList'
|
||||||
import {Button} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import {FormContainer} from './FormContainer'
|
import {FormContainer} from './FormContainer'
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ export const ChooseAccountForm = ({
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
size="medium"
|
size="medium"
|
||||||
onPress={onPressBack}>
|
onPress={onPressBack}>
|
||||||
{_(msg`Back`)}
|
<ButtonText>{_(msg`Back`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<View style={[a.flex_1]} />
|
<View style={[a.flex_1]} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -21,9 +21,7 @@ export const LoginForm = ({
|
|||||||
error,
|
error,
|
||||||
serviceUrl,
|
serviceUrl,
|
||||||
serviceDescription,
|
serviceDescription,
|
||||||
setError,
|
|
||||||
setServiceUrl,
|
setServiceUrl,
|
||||||
onPressRetryConnect,
|
|
||||||
onPressBack,
|
onPressBack,
|
||||||
}: {
|
}: {
|
||||||
error: string
|
error: string
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
web,
|
web,
|
||||||
} from '#/alf'
|
} from '#/alf'
|
||||||
import {Button, ButtonIcon} from '#/components/Button'
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||||
import {createPortalGroup} from '#/components/Portal'
|
import {createPortalGroup} from '#/components/Portal'
|
||||||
import {leading, P, Text} from '#/components/Typography'
|
import {leading, P, Text} from '#/components/Typography'
|
||||||
@@ -73,7 +73,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
|||||||
onPress={() => onboardDispatch({type: 'skip'})}
|
onPress={() => onboardDispatch({type: 'skip'})}
|
||||||
// DEV ONLY
|
// DEV ONLY
|
||||||
label="Clear onboarding state">
|
label="Clear onboarding state">
|
||||||
Clear
|
<ButtonText>Clear</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {LabelPreference, InterpretedLabelValueDefinition} from '@atproto/api'
|
import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api'
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
usePreferencesSetContentLabelMutation,
|
usePreferencesSetContentLabelMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Text} from '#/components/Typography'
|
|
||||||
import * as ToggleButton from '#/components/forms/ToggleButton'
|
import * as ToggleButton from '#/components/forms/ToggleButton'
|
||||||
import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings'
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
export function ModerationOption({
|
export function ModerationOption({
|
||||||
labelValueDefinition,
|
labelValueDefinition,
|
||||||
@@ -83,13 +83,13 @@ export function ModerationOption({
|
|||||||
values={[visibility ?? 'hide']}
|
values={[visibility ?? 'hide']}
|
||||||
onChange={onChange}>
|
onChange={onChange}>
|
||||||
<ToggleButton.Button name="ignore" label={labels.show}>
|
<ToggleButton.Button name="ignore" label={labels.show}>
|
||||||
{labels.show}
|
<ToggleButton.ButtonText>{labels.show}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="warn" label={labels.warn}>
|
<ToggleButton.Button name="warn" label={labels.warn}>
|
||||||
{labels.warn}
|
<ToggleButton.ButtonText>{labels.warn}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="hide" label={labels.hide}>
|
<ToggleButton.Button name="hide" label={labels.hide}>
|
||||||
{labels.hide}
|
<ToggleButton.ButtonText>{labels.hide}</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {
|
import {
|
||||||
@@ -78,6 +80,9 @@ let ProfileHeaderStandard = ({
|
|||||||
})
|
})
|
||||||
}, [track, openModal, profile])
|
}, [track, openModal, profile])
|
||||||
|
|
||||||
|
const autoExpandSuggestionsOnProfileFollow = useGate(
|
||||||
|
'autoexpand_suggestions_on_profile_follow',
|
||||||
|
)
|
||||||
const onPressFollow = () => {
|
const onPressFollow = () => {
|
||||||
requireAuth(async () => {
|
requireAuth(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -91,6 +96,9 @@ let ProfileHeaderStandard = ({
|
|||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
if (isWeb && autoExpandSuggestionsOnProfileFollow) {
|
||||||
|
setShowSuggestedFollows(true)
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to follow', {message: String(e)})
|
logger.error('Failed to follow', {message: String(e)})
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import {useCallback} from 'react'
|
import {useCallback} from 'react'
|
||||||
import {AppBskyFeedDefs, AtUri} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
|
||||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {track} from '#/lib/analytics/analytics'
|
import {track} from '#/lib/analytics/analytics'
|
||||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||||
import {updatePostShadow} from '#/state/cache/post-shadow'
|
import {updatePostShadow} from '#/state/cache/post-shadow'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
import {getAgent} from '#/state/session'
|
import {getAgent, useSession} from '#/state/session'
|
||||||
|
import {findProfileQueryData} from './profile'
|
||||||
|
|
||||||
const RQKEY_ROOT = 'post'
|
const RQKEY_ROOT = 'post'
|
||||||
export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]
|
export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri]
|
||||||
@@ -68,7 +69,7 @@ export function usePostLikeMutationQueue(
|
|||||||
const postUri = post.uri
|
const postUri = post.uri
|
||||||
const postCid = post.cid
|
const postCid = post.cid
|
||||||
const initialLikeUri = post.viewer?.like
|
const initialLikeUri = post.viewer?.like
|
||||||
const likeMutation = usePostLikeMutation(logContext)
|
const likeMutation = usePostLikeMutation(logContext, post)
|
||||||
const unlikeMutation = usePostUnlikeMutation(logContext)
|
const unlikeMutation = usePostUnlikeMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
@@ -117,15 +118,40 @@ export function usePostLikeMutationQueue(
|
|||||||
return [queueLike, queueUnlike]
|
return [queueLike, queueUnlike]
|
||||||
}
|
}
|
||||||
|
|
||||||
function usePostLikeMutation(logContext: LogEvents['post:like']['logContext']) {
|
function usePostLikeMutation(
|
||||||
|
logContext: LogEvents['post:like']['logContext'],
|
||||||
|
post: Shadow<AppBskyFeedDefs.PostView>,
|
||||||
|
) {
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const postAuthor = post.author
|
||||||
return useMutation<
|
return useMutation<
|
||||||
{uri: string}, // responds with the uri of the like
|
{uri: string}, // responds with the uri of the like
|
||||||
Error,
|
Error,
|
||||||
{uri: string; cid: string} // the post's uri and cid
|
{uri: string; cid: string} // the post's uri and cid
|
||||||
>({
|
>({
|
||||||
mutationFn: post => {
|
mutationFn: ({uri, cid}) => {
|
||||||
logEvent('post:like', {logContext})
|
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||||
return getAgent().like(post.uri, post.cid)
|
if (currentAccount) {
|
||||||
|
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||||
|
}
|
||||||
|
logEvent('post:like', {
|
||||||
|
logContext,
|
||||||
|
doesPosterFollowLiker: postAuthor.viewer
|
||||||
|
? Boolean(postAuthor.viewer.followedBy)
|
||||||
|
: undefined,
|
||||||
|
doesLikerFollowPoster: postAuthor.viewer
|
||||||
|
? Boolean(postAuthor.viewer.following)
|
||||||
|
: undefined,
|
||||||
|
likerClout: toClout(ownProfile?.followersCount),
|
||||||
|
postClout:
|
||||||
|
post.likeCount != null &&
|
||||||
|
post.repostCount != null &&
|
||||||
|
post.replyCount != null
|
||||||
|
? toClout(post.likeCount + post.repostCount + post.replyCount)
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
return getAgent().like(uri, cid)
|
||||||
},
|
},
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
track('Post:Like')
|
track('Post:Like')
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {track} from '#/lib/analytics/analytics'
|
|||||||
import {uploadBlob} from '#/lib/api'
|
import {uploadBlob} from '#/lib/api'
|
||||||
import {until} from '#/lib/async/until'
|
import {until} from '#/lib/async/until'
|
||||||
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
|
||||||
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
|
import {logEvent, LogEvents, toClout} from '#/lib/statsig/statsig'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
import {resetProfilePostsQueries} from '#/state/queries/post-feed'
|
||||||
@@ -202,7 +202,7 @@ export function useProfileFollowMutationQueue(
|
|||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const did = profile.did
|
const did = profile.did
|
||||||
const initialFollowingUri = profile.viewer?.following
|
const initialFollowingUri = profile.viewer?.following
|
||||||
const followMutation = useProfileFollowMutation(logContext)
|
const followMutation = useProfileFollowMutation(logContext, profile)
|
||||||
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
const unfollowMutation = useProfileUnfollowMutation(logContext)
|
||||||
|
|
||||||
const queueToggle = useToggleMutationQueue({
|
const queueToggle = useToggleMutationQueue({
|
||||||
@@ -252,10 +252,24 @@ export function useProfileFollowMutationQueue(
|
|||||||
|
|
||||||
function useProfileFollowMutation(
|
function useProfileFollowMutation(
|
||||||
logContext: LogEvents['profile:follow']['logContext'],
|
logContext: LogEvents['profile:follow']['logContext'],
|
||||||
|
profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>,
|
||||||
) {
|
) {
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
return useMutation<{uri: string; cid: string}, Error, {did: string}>({
|
||||||
mutationFn: async ({did}) => {
|
mutationFn: async ({did}) => {
|
||||||
logEvent('profile:follow', {logContext})
|
let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined
|
||||||
|
if (currentAccount) {
|
||||||
|
ownProfile = findProfileQueryData(queryClient, currentAccount.did)
|
||||||
|
}
|
||||||
|
logEvent('profile:follow', {
|
||||||
|
logContext,
|
||||||
|
didBecomeMutual: profile.viewer
|
||||||
|
? Boolean(profile.viewer.followedBy)
|
||||||
|
: undefined,
|
||||||
|
followeeClout: toClout(profile.followersCount),
|
||||||
|
followerClout: toClout(ownProfile?.followersCount),
|
||||||
|
})
|
||||||
return await getAgent().follow(did)
|
return await getAgent().follow(did)
|
||||||
},
|
},
|
||||||
onSuccess(data, variables) {
|
onSuccess(data, variables) {
|
||||||
@@ -530,3 +544,12 @@ export function* findAllProfilesInQueryData(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function findProfileQueryData(
|
||||||
|
queryClient: QueryClient,
|
||||||
|
did: string,
|
||||||
|
): AppBskyActorDefs.ProfileViewDetailed | undefined {
|
||||||
|
return queryClient.getQueryData<AppBskyActorDefs.ProfileViewDetailed>(
|
||||||
|
RQKEY(did),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import * as persisted from '#/state/persisted'
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {isWeb} from '#/platform/detection'
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import * as persisted from '#/state/persisted'
|
||||||
|
|
||||||
type StateContext = string
|
type StateContext = string
|
||||||
type SetContext = (v: string) => void
|
type SetContext = (v: string) => void
|
||||||
@@ -8,7 +10,7 @@ type SetContext = (v: string) => void
|
|||||||
const stateContext = React.createContext<StateContext>('home')
|
const stateContext = React.createContext<StateContext>('home')
|
||||||
const setContext = React.createContext<SetContext>((_: string) => {})
|
const setContext = React.createContext<SetContext>((_: string) => {})
|
||||||
|
|
||||||
function getInitialFeed() {
|
function getInitialFeed(startSessionWithFollowing: boolean) {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
if (window.location.pathname === '/') {
|
if (window.location.pathname === '/') {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
@@ -24,16 +26,21 @@ function getInitialFeed() {
|
|||||||
return feedFromSession
|
return feedFromSession
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!startSessionWithFollowing) {
|
||||||
const feedFromPersisted = persisted.get('lastSelectedHomeFeed')
|
const feedFromPersisted = persisted.get('lastSelectedHomeFeed')
|
||||||
if (feedFromPersisted) {
|
if (feedFromPersisted) {
|
||||||
// Fall back to the last chosen one across all tabs.
|
// Fall back to the last chosen one across all tabs.
|
||||||
return feedFromPersisted
|
return feedFromPersisted
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 'home'
|
return 'home'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
const [state, setState] = React.useState(getInitialFeed)
|
const startSessionWithFollowing = useGate('start_session_with_following')
|
||||||
|
const [state, setState] = React.useState(() =>
|
||||||
|
getInitialFeed(startSessionWithFollowing),
|
||||||
|
)
|
||||||
|
|
||||||
const saveState = React.useCallback((feed: string) => {
|
const saveState = React.useCallback((feed: string) => {
|
||||||
setState(feed)
|
setState(feed)
|
||||||
|
|||||||
@@ -87,13 +87,17 @@ export function ServerInputDialog({
|
|||||||
values={fixedOption}
|
values={fixedOption}
|
||||||
onChange={setFixedOption}>
|
onChange={setFixedOption}>
|
||||||
<ToggleButton.Button name={BSKY_SERVICE} label={_(msg`Bluesky`)}>
|
<ToggleButton.Button name={BSKY_SERVICE} label={_(msg`Bluesky`)}>
|
||||||
|
<ToggleButton.ButtonText>
|
||||||
{_(msg`Bluesky`)}
|
{_(msg`Bluesky`)}
|
||||||
|
</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button
|
<ToggleButton.Button
|
||||||
testID="customSelectBtn"
|
testID="customSelectBtn"
|
||||||
name="custom"
|
name="custom"
|
||||||
label={_(msg`Custom`)}>
|
label={_(msg`Custom`)}>
|
||||||
|
<ToggleButton.ButtonText>
|
||||||
{_(msg`Custom`)}
|
{_(msg`Custom`)}
|
||||||
|
</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
|
|
||||||
@@ -163,7 +167,7 @@ export function ServerInputDialog({
|
|||||||
size="small"
|
size="small"
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
label={_(msg`Done`)}>
|
label={_(msg`Done`)}>
|
||||||
{_(msg`Done`)}
|
<ButtonText>{_(msg`Done`)}</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -508,11 +508,7 @@ export const ComposePost = observer(function ComposePost({
|
|||||||
title={_(msg`Discard draft?`)}
|
title={_(msg`Discard draft?`)}
|
||||||
description={_(msg`Are you sure you'd like to discard this draft?`)}
|
description={_(msg`Are you sure you'd like to discard this draft?`)}
|
||||||
onConfirm={() => {
|
onConfirm={() => {
|
||||||
if (isWeb) {
|
|
||||||
onClose()
|
|
||||||
} else {
|
|
||||||
discardPromptControl.close(onClose)
|
discardPromptControl.close(onClose)
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
confirmButtonCta={_(msg`Discard`)}
|
confirmButtonCta={_(msg`Discard`)}
|
||||||
confirmButtonColor="negative"
|
confirmButtonColor="negative"
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useWindowDimensions, View} from 'react-native'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
|
||||||
import {MainScrollProvider} from '../util/MainScrollProvider'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
|
||||||
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
|
||||||
import {ComposeIcon2} from 'lib/icons'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {View, useWindowDimensions} from 'react-native'
|
|
||||||
import {ListMethods} from '../util/List'
|
|
||||||
import {Feed} from '../posts/Feed'
|
|
||||||
import {FAB} from '../util/fab/FAB'
|
|
||||||
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useSession} from '#/state/session'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {listenSoftReset} from '#/state/events'
|
|
||||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
import {getRootNavigation, getTabState, TabState} from '#/lib/routes/helpers'
|
||||||
import {TabState, getTabState, getRootNavigation} from '#/lib/routes/helpers'
|
import {logEvent, useGate} from '#/lib/statsig/statsig'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {listenSoftReset} from '#/state/events'
|
||||||
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
|
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
||||||
|
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {ComposeIcon2} from 'lib/icons'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
import {Feed} from '../posts/Feed'
|
||||||
|
import {FAB} from '../util/fab/FAB'
|
||||||
|
import {ListMethods} from '../util/List'
|
||||||
|
import {LoadLatestBtn} from '../util/load-latest/LoadLatestBtn'
|
||||||
|
import {MainScrollProvider} from '../util/MainScrollProvider'
|
||||||
|
|
||||||
const POLL_FREQ = 60e3 // 60sec
|
const POLL_FREQ = 60e3 // 60sec
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ export function FeedPage({
|
|||||||
setHasNew(false)
|
setHasNew(false)
|
||||||
logEvent('feed:refresh', {
|
logEvent('feed:refresh', {
|
||||||
feedType: feed.split('|')[0],
|
feedType: feed.split('|')[0],
|
||||||
|
feedUrl: feed,
|
||||||
reason: 'soft-reset',
|
reason: 'soft-reset',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -96,10 +98,22 @@ export function FeedPage({
|
|||||||
setHasNew(false)
|
setHasNew(false)
|
||||||
logEvent('feed:refresh', {
|
logEvent('feed:refresh', {
|
||||||
feedType: feed.split('|')[0],
|
feedType: feed.split('|')[0],
|
||||||
|
feedUrl: feed,
|
||||||
reason: 'load-latest',
|
reason: 'load-latest',
|
||||||
})
|
})
|
||||||
}, [scrollToTop, feed, queryClient, setHasNew])
|
}, [scrollToTop, feed, queryClient, setHasNew])
|
||||||
|
|
||||||
|
let feedPollInterval
|
||||||
|
if (
|
||||||
|
useGate('disable_poll_on_discover') &&
|
||||||
|
feed === // Discover
|
||||||
|
'feedgen|at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.generator/whats-hot'
|
||||||
|
) {
|
||||||
|
feedPollInterval = undefined
|
||||||
|
} else {
|
||||||
|
feedPollInterval = POLL_FREQ
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View testID={testID} style={s.h100pct}>
|
<View testID={testID} style={s.h100pct}>
|
||||||
<MainScrollProvider>
|
<MainScrollProvider>
|
||||||
@@ -108,7 +122,7 @@ export function FeedPage({
|
|||||||
enabled={isPageFocused}
|
enabled={isPageFocused}
|
||||||
feed={feed}
|
feed={feed}
|
||||||
feedParams={feedParams}
|
feedParams={feedParams}
|
||||||
pollInterval={POLL_FREQ}
|
pollInterval={feedPollInterval}
|
||||||
disablePoll={hasNew}
|
disablePoll={hasNew}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
onScrolledDownChange={setIsScrolledDown}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, TouchableOpacity, View} from 'react-native'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
|
||||||
import {AppBskyActorDefs} from '@atproto/api'
|
import {AppBskyActorDefs} from '@atproto/api'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {track} from 'lib/analytics/analytics'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
import {Shadow, useProfileShadow} from 'state/cache/profile-shadow'
|
import {Shadow, useProfileShadow} from 'state/cache/profile-shadow'
|
||||||
import {track} from 'lib/analytics/analytics'
|
|
||||||
import {
|
import {
|
||||||
useProfileFollowMutationQueue,
|
useProfileFollowMutationQueue,
|
||||||
useProfileQuery,
|
useProfileQuery,
|
||||||
} from 'state/queries/profile'
|
} from 'state/queries/profile'
|
||||||
import {useRequireAuth} from 'state/session'
|
import {useRequireAuth} from 'state/session'
|
||||||
|
import {Text} from 'view/com/util/text/Text'
|
||||||
|
import * as Toast from 'view/com/util/Toast'
|
||||||
|
|
||||||
export function PostThreadFollowBtn({did}: {did: string}) {
|
export function PostThreadFollowBtn({did}: {did: string}) {
|
||||||
const {data: profile, isLoading} = useProfileQuery({did})
|
const {data: profile, isLoading} = useProfileQuery({did})
|
||||||
@@ -47,8 +48,10 @@ function PostThreadFollowBtnLoaded({
|
|||||||
'PostThreadItem',
|
'PostThreadItem',
|
||||||
)
|
)
|
||||||
const requireAuth = useRequireAuth()
|
const requireAuth = useRequireAuth()
|
||||||
|
const showFollowBackLabel = useGate('show_follow_back_label')
|
||||||
|
|
||||||
const isFollowing = !!profile.viewer?.following
|
const isFollowing = !!profile.viewer?.following
|
||||||
|
const isFollowedBy = !!profile.viewer?.followedBy
|
||||||
const [wasFollowing, setWasFollowing] = React.useState<boolean>(isFollowing)
|
const [wasFollowing, setWasFollowing] = React.useState<boolean>(isFollowing)
|
||||||
|
|
||||||
// This prevents the button from disappearing as soon as we follow.
|
// This prevents the button from disappearing as soon as we follow.
|
||||||
@@ -136,7 +139,15 @@ function PostThreadFollowBtnLoaded({
|
|||||||
type="button"
|
type="button"
|
||||||
style={[!isFollowing ? palInverted.text : pal.text, s.bold]}
|
style={[!isFollowing ? palInverted.text : pal.text, s.bold]}
|
||||||
numberOfLines={1}>
|
numberOfLines={1}>
|
||||||
{!isFollowing ? <Trans>Follow</Trans> : <Trans>Following</Trans>}
|
{!isFollowing ? (
|
||||||
|
showFollowBackLabel && isFollowedBy ? (
|
||||||
|
<Trans>Follow Back</Trans>
|
||||||
|
) : (
|
||||||
|
<Trans>Follow</Trans>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Trans>Following</Trans>
|
||||||
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
+27
-23
@@ -8,32 +8,33 @@ import {
|
|||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
|
||||||
import {List, ListRef} from '../util/List'
|
|
||||||
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
|
||||||
import {FeedErrorMessage} from './FeedErrorMessage'
|
|
||||||
import {FeedSlice} from './FeedSlice'
|
|
||||||
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {
|
|
||||||
RQKEY,
|
|
||||||
FeedDescriptor,
|
|
||||||
FeedParams,
|
|
||||||
usePostFeedQuery,
|
|
||||||
pollLatest,
|
|
||||||
} from '#/state/queries/post-feed'
|
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {listenPostCreated} from '#/state/events'
|
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {STALE} from '#/state/queries'
|
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
|
import {FALLBACK_MARKER_POST} from '#/lib/api/feed/home'
|
||||||
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
|
||||||
import {logEvent} from '#/lib/statsig/statsig'
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
|
import {listenPostCreated} from '#/state/events'
|
||||||
|
import {STALE} from '#/state/queries'
|
||||||
|
import {
|
||||||
|
FeedDescriptor,
|
||||||
|
FeedParams,
|
||||||
|
pollLatest,
|
||||||
|
RQKEY,
|
||||||
|
usePostFeedQuery,
|
||||||
|
} from '#/state/queries/post-feed'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
|
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
|
||||||
|
import {useTheme} from 'lib/ThemeContext'
|
||||||
|
import {List, ListRef} from '../util/List'
|
||||||
|
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
|
||||||
|
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
|
||||||
|
import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
|
||||||
|
import {FeedErrorMessage} from './FeedErrorMessage'
|
||||||
|
import {FeedSlice} from './FeedSlice'
|
||||||
|
|
||||||
const LOADING_ITEM = {_reactKey: '__loading__'}
|
const LOADING_ITEM = {_reactKey: '__loading__'}
|
||||||
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
|
||||||
@@ -217,6 +218,7 @@ let Feed = ({
|
|||||||
track('Feed:onRefresh')
|
track('Feed:onRefresh')
|
||||||
logEvent('feed:refresh', {
|
logEvent('feed:refresh', {
|
||||||
feedType: feedType,
|
feedType: feedType,
|
||||||
|
feedUrl: feed,
|
||||||
reason: 'pull-to-refresh',
|
reason: 'pull-to-refresh',
|
||||||
})
|
})
|
||||||
setIsPTRing(true)
|
setIsPTRing(true)
|
||||||
@@ -227,13 +229,14 @@ let Feed = ({
|
|||||||
logger.error('Failed to refresh posts feed', {message: err})
|
logger.error('Failed to refresh posts feed', {message: err})
|
||||||
}
|
}
|
||||||
setIsPTRing(false)
|
setIsPTRing(false)
|
||||||
}, [refetch, track, setIsPTRing, onHasNew, feedType])
|
}, [refetch, track, setIsPTRing, onHasNew, feed, feedType])
|
||||||
|
|
||||||
const onEndReached = React.useCallback(async () => {
|
const onEndReached = React.useCallback(async () => {
|
||||||
if (isFetching || !hasNextPage || isError) return
|
if (isFetching || !hasNextPage || isError) return
|
||||||
|
|
||||||
logEvent('feed:endReached', {
|
logEvent('feed:endReached', {
|
||||||
feedType: feedType,
|
feedType: feedType,
|
||||||
|
feedUrl: feed,
|
||||||
itemCount: feedItems.length,
|
itemCount: feedItems.length,
|
||||||
})
|
})
|
||||||
track('Feed:onEndReached')
|
track('Feed:onEndReached')
|
||||||
@@ -248,6 +251,7 @@ let Feed = ({
|
|||||||
isError,
|
isError,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
track,
|
track,
|
||||||
|
feed,
|
||||||
feedType,
|
feedType,
|
||||||
feedItems.length,
|
feedItems.length,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -274,13 +274,13 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
|||||||
values={scenario}
|
values={scenario}
|
||||||
onChange={setScenario}>
|
onChange={setScenario}>
|
||||||
<ToggleButton.Button name="label" label="Label">
|
<ToggleButton.Button name="label" label="Label">
|
||||||
Label
|
<ToggleButton.ButtonText>Label</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="block" label="Block">
|
<ToggleButton.Button name="block" label="Block">
|
||||||
Block
|
<ToggleButton.ButtonText>Block</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="mute" label="Mute">
|
<ToggleButton.Button name="mute" label="Mute">
|
||||||
Mute
|
<ToggleButton.ButtonText>Mute</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
|
|
||||||
@@ -474,16 +474,16 @@ export const DebugModScreen = ({}: NativeStackScreenProps<
|
|||||||
|
|
||||||
<ToggleButton.Group label="Results" values={view} onChange={setView}>
|
<ToggleButton.Group label="Results" values={view} onChange={setView}>
|
||||||
<ToggleButton.Button name="post" label="Post">
|
<ToggleButton.Button name="post" label="Post">
|
||||||
Post
|
<ToggleButton.ButtonText>Post</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="notifications" label="Notifications">
|
<ToggleButton.Button name="notifications" label="Notifications">
|
||||||
Notifications
|
<ToggleButton.ButtonText>Notifications</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="account" label="Account">
|
<ToggleButton.Button name="account" label="Account">
|
||||||
Account
|
<ToggleButton.ButtonText>Account</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="data" label="Data">
|
<ToggleButton.Button name="data" label="Data">
|
||||||
Data
|
<ToggleButton.ButtonText>Data</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
|
|
||||||
|
|||||||
+32
-14
@@ -1,23 +1,25 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View, ActivityIndicator, StyleSheet} from 'react-native'
|
import {ActivityIndicator, AppState, StyleSheet, View} from 'react-native'
|
||||||
import {useFocusEffect} from '@react-navigation/native'
|
import {useFocusEffect} from '@react-navigation/native'
|
||||||
import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
|
|
||||||
|
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
|
import {emitSoftReset} from '#/state/events'
|
||||||
|
import {FeedSourceInfo, usePinnedFeedsInfos} from '#/state/queries/feed'
|
||||||
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
|
||||||
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
|
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {useSetDrawerSwipeDisabled, useSetMinimalShellMode} from '#/state/shell'
|
||||||
|
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
||||||
|
import {HomeTabNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||||
|
import {FeedPage} from 'view/com/feeds/FeedPage'
|
||||||
|
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
|
||||||
|
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
||||||
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
|
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
|
||||||
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
|
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
|
||||||
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
|
|
||||||
import {HomeHeader} from '../com/home/HomeHeader'
|
|
||||||
import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
|
|
||||||
import {FeedPage} from 'view/com/feeds/FeedPage'
|
|
||||||
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
|
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
|
||||||
import {useSetMinimalShellMode, useSetDrawerSwipeDisabled} from '#/state/shell'
|
import {HomeHeader} from '../com/home/HomeHeader'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
|
||||||
import {usePinnedFeedsInfos, FeedSourceInfo} from '#/state/queries/feed'
|
|
||||||
import {UsePreferencesQueryResponse} from '#/state/queries/preferences/types'
|
|
||||||
import {emitSoftReset} from '#/state/events'
|
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {useSelectedFeed, useSetSelectedFeed} from '#/state/shell/selected-feed'
|
|
||||||
import {useSetTitle} from '#/lib/hooks/useSetTitle'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||||
export function HomeScreen(props: Props) {
|
export function HomeScreen(props: Props) {
|
||||||
@@ -94,6 +96,22 @@ function HomeScreenReady({
|
|||||||
}, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]),
|
}, [setDrawerSwipeDisabled, selectedIndex, setMinimalShellMode]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const disableMinShellOnForegrounding = useGate(
|
||||||
|
'disable_min_shell_on_foregrounding',
|
||||||
|
)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (disableMinShellOnForegrounding) {
|
||||||
|
const listener = AppState.addEventListener('change', nextAppState => {
|
||||||
|
if (nextAppState === 'active') {
|
||||||
|
setMinimalShellMode(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
listener.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [setMinimalShellMode, disableMinShellOnForegrounding])
|
||||||
|
|
||||||
const onPageSelected = React.useCallback(
|
const onPageSelected = React.useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
setMinimalShellMode(false)
|
setMinimalShellMode(false)
|
||||||
|
|||||||
@@ -92,7 +92,9 @@ export function ExportCarDialog({
|
|||||||
size={gtMobile ? 'small' : 'large'}
|
size={gtMobile ? 'small' : 'large'}
|
||||||
onPress={() => control.close()}
|
onPress={() => control.close()}
|
||||||
label={_(msg`Done`)}>
|
label={_(msg`Done`)}>
|
||||||
{_(msg`Done`)}
|
<ButtonText>
|
||||||
|
<Trans>Done</Trans>
|
||||||
|
</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import {View} from 'react-native'
|
|||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
ButtonVariant,
|
|
||||||
ButtonColor,
|
ButtonColor,
|
||||||
ButtonIcon,
|
ButtonIcon,
|
||||||
ButtonText,
|
ButtonText,
|
||||||
|
ButtonVariant,
|
||||||
} from '#/components/Button'
|
} from '#/components/Button'
|
||||||
import {H1} from '#/components/Typography'
|
|
||||||
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
|
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
|
||||||
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||||
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
||||||
|
import {H1} from '#/components/Typography'
|
||||||
|
|
||||||
export function Buttons() {
|
export function Buttons() {
|
||||||
return (
|
return (
|
||||||
@@ -29,7 +29,7 @@ export function Buttons() {
|
|||||||
color={color as ButtonColor}
|
color={color as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
@@ -37,7 +37,7 @@ export function Buttons() {
|
|||||||
color={color as ButtonColor}
|
color={color as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
@@ -54,7 +54,7 @@ export function Buttons() {
|
|||||||
color={name as ButtonColor}
|
color={name as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
@@ -62,7 +62,7 @@ export function Buttons() {
|
|||||||
color={name as ButtonColor}
|
color={name as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
@@ -77,7 +77,7 @@ export function Buttons() {
|
|||||||
color={name as ButtonColor}
|
color={name as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled
|
disabled
|
||||||
@@ -85,7 +85,7 @@ export function Buttons() {
|
|||||||
color={name as ButtonColor}
|
color={name as ButtonColor}
|
||||||
size="large"
|
size="large"
|
||||||
label="Click here">
|
label="Click here">
|
||||||
Button
|
<ButtonText>Button</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {View} from 'react-native'
|
|||||||
|
|
||||||
import {useDialogStateControlContext} from '#/state/dialogs'
|
import {useDialogStateControlContext} from '#/state/dialogs'
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Dialog from '#/components/Dialog'
|
import * as Dialog from '#/components/Dialog'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
import {H3, P} from '#/components/Typography'
|
import {H3, P} from '#/components/Typography'
|
||||||
@@ -26,7 +26,7 @@ export function Dialogs() {
|
|||||||
basic.open()
|
basic.open()
|
||||||
}}
|
}}
|
||||||
label="Open basic dialog">
|
label="Open basic dialog">
|
||||||
Open all dialogs
|
<ButtonText>Open all dialogs</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -37,7 +37,7 @@ export function Dialogs() {
|
|||||||
scrollable.open()
|
scrollable.open()
|
||||||
}}
|
}}
|
||||||
label="Open basic dialog">
|
label="Open basic dialog">
|
||||||
Open scrollable dialog
|
<ButtonText>Open scrollable dialog</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -48,7 +48,7 @@ export function Dialogs() {
|
|||||||
basic.open()
|
basic.open()
|
||||||
}}
|
}}
|
||||||
label="Open basic dialog">
|
label="Open basic dialog">
|
||||||
Open basic dialog
|
<ButtonText>Open basic dialog</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -57,7 +57,7 @@ export function Dialogs() {
|
|||||||
size="small"
|
size="small"
|
||||||
onPress={() => prompt.open()}
|
onPress={() => prompt.open()}
|
||||||
label="Open prompt">
|
label="Open prompt">
|
||||||
Open prompt
|
<ButtonText>Open prompt</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Prompt.Outer control={prompt}>
|
<Prompt.Outer control={prompt}>
|
||||||
@@ -67,8 +67,8 @@ export function Dialogs() {
|
|||||||
description, as well as two actions.
|
description, as well as two actions.
|
||||||
</Prompt.DescriptionText>
|
</Prompt.DescriptionText>
|
||||||
<Prompt.Actions>
|
<Prompt.Actions>
|
||||||
<Prompt.Cancel>Cancel</Prompt.Cancel>
|
<Prompt.Cancel />
|
||||||
<Prompt.Action onPress={() => {}}>Confirm</Prompt.Action>
|
<Prompt.Action cta="Confirm" onPress={() => {}} />
|
||||||
</Prompt.Actions>
|
</Prompt.Actions>
|
||||||
</Prompt.Outer>
|
</Prompt.Outer>
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ export function Dialogs() {
|
|||||||
size="small"
|
size="small"
|
||||||
onPress={closeAllDialogs}
|
onPress={closeAllDialogs}
|
||||||
label="Close all dialogs">
|
label="Close all dialogs">
|
||||||
Close all dialogs
|
<ButtonText>Close all dialogs</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<View style={{height: 1000}} />
|
<View style={{height: 1000}} />
|
||||||
<View style={[a.flex_row, a.justify_end]}>
|
<View style={[a.flex_row, a.justify_end]}>
|
||||||
@@ -116,7 +116,7 @@ export function Dialogs() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
label="Open basic dialog">
|
label="Open basic dialog">
|
||||||
Close dialog
|
<ButtonText>Close dialog</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react'
|
|||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
import {DateField, LabelText} from '#/components/forms/DateField'
|
import {DateField, LabelText} from '#/components/forms/DateField'
|
||||||
import * as TextField from '#/components/forms/TextField'
|
import * as TextField from '#/components/forms/TextField'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
@@ -191,7 +191,7 @@ export function Forms() {
|
|||||||
setToggleGroupBValues(['a', 'b'])
|
setToggleGroupBValues(['a', 'b'])
|
||||||
setToggleGroupCValues(['a'])
|
setToggleGroupCValues(['a'])
|
||||||
}}>
|
}}>
|
||||||
Reset all toggles
|
<ButtonText>Reset all toggles</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<View style={[a.gap_md, a.align_start, a.w_full]}>
|
<View style={[a.gap_md, a.align_start, a.w_full]}>
|
||||||
@@ -202,13 +202,13 @@ export function Forms() {
|
|||||||
values={toggleGroupDValues}
|
values={toggleGroupDValues}
|
||||||
onChange={setToggleGroupDValues}>
|
onChange={setToggleGroupDValues}>
|
||||||
<ToggleButton.Button name="hide" label="Hide">
|
<ToggleButton.Button name="hide" label="Hide">
|
||||||
Hide
|
<ToggleButton.ButtonText>Hide</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="warn" label="Warn">
|
<ToggleButton.Button name="warn" label="Warn">
|
||||||
Warn
|
<ToggleButton.ButtonText>Warn</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="show" label="Show">
|
<ToggleButton.Button name="show" label="Show">
|
||||||
Show
|
<ToggleButton.ButtonText>Show</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
|
|
||||||
@@ -218,13 +218,13 @@ export function Forms() {
|
|||||||
values={toggleGroupDValues}
|
values={toggleGroupDValues}
|
||||||
onChange={setToggleGroupDValues}>
|
onChange={setToggleGroupDValues}>
|
||||||
<ToggleButton.Button name="hide" label="Hide">
|
<ToggleButton.Button name="hide" label="Hide">
|
||||||
Hide
|
<ToggleButton.ButtonText>Hide</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="warn" label="Warn">
|
<ToggleButton.Button name="warn" label="Warn">
|
||||||
Warn
|
<ToggleButton.ButtonText>Warn</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
<ToggleButton.Button name="show" label="Show">
|
<ToggleButton.Button name="show" label="Show">
|
||||||
Show
|
<ToggleButton.ButtonText>Show</ToggleButton.ButtonText>
|
||||||
</ToggleButton.Button>
|
</ToggleButton.Button>
|
||||||
</ToggleButton.Group>
|
</ToggleButton.Group>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
|
||||||
|
|
||||||
import {atoms as a, useTheme, ThemeProvider} from '#/alf'
|
|
||||||
import {useSetThemePrefs} from '#/state/shell'
|
import {useSetThemePrefs} from '#/state/shell'
|
||||||
import {Button} from '#/components/Button'
|
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||||
|
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
||||||
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
|
import {Breakpoints} from './Breakpoints'
|
||||||
|
import {Buttons} from './Buttons'
|
||||||
|
import {Dialogs} from './Dialogs'
|
||||||
|
import {Forms} from './Forms'
|
||||||
|
import {Icons} from './Icons'
|
||||||
|
import {Links} from './Links'
|
||||||
|
import {Menus} from './Menus'
|
||||||
|
import {Shadows} from './Shadows'
|
||||||
|
import {Spacing} from './Spacing'
|
||||||
import {Theming} from './Theming'
|
import {Theming} from './Theming'
|
||||||
import {Typography} from './Typography'
|
import {Typography} from './Typography'
|
||||||
import {Spacing} from './Spacing'
|
|
||||||
import {Buttons} from './Buttons'
|
|
||||||
import {Links} from './Links'
|
|
||||||
import {Forms} from './Forms'
|
|
||||||
import {Dialogs} from './Dialogs'
|
|
||||||
import {Breakpoints} from './Breakpoints'
|
|
||||||
import {Shadows} from './Shadows'
|
|
||||||
import {Icons} from './Icons'
|
|
||||||
import {Menus} from './Menus'
|
|
||||||
|
|
||||||
export function Storybook() {
|
export function Storybook() {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
@@ -33,7 +32,7 @@ export function Storybook() {
|
|||||||
size="small"
|
size="small"
|
||||||
label='Set theme to "system"'
|
label='Set theme to "system"'
|
||||||
onPress={() => setColorMode('system')}>
|
onPress={() => setColorMode('system')}>
|
||||||
System
|
<ButtonText>System</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -41,7 +40,7 @@ export function Storybook() {
|
|||||||
size="small"
|
size="small"
|
||||||
label='Set theme to "light"'
|
label='Set theme to "light"'
|
||||||
onPress={() => setColorMode('light')}>
|
onPress={() => setColorMode('light')}>
|
||||||
Light
|
<ButtonText>Light</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -52,7 +51,7 @@ export function Storybook() {
|
|||||||
setColorMode('dark')
|
setColorMode('dark')
|
||||||
setDarkTheme('dim')
|
setDarkTheme('dim')
|
||||||
}}>
|
}}>
|
||||||
Dim
|
<ButtonText>Dim</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -63,7 +62,7 @@ export function Storybook() {
|
|||||||
setColorMode('dark')
|
setColorMode('dark')
|
||||||
setDarkTheme('dark')
|
setDarkTheme('dark')
|
||||||
}}>
|
}}>
|
||||||
Dark
|
<ButtonText>Dark</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -4836,10 +4836,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
|
|
||||||
"@react-native-async-storage/async-storage@1.21.0":
|
"@react-native-async-storage/async-storage@1.23.1":
|
||||||
version "1.21.0"
|
version "1.23.1"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.21.0.tgz#d7e370028e228ab84637016ceeb495878b7a44c8"
|
resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.23.1.tgz#cad3cd4fab7dacfe9838dce6ecb352f79150c883"
|
||||||
integrity sha512-JL0w36KuFHFCvnbOXRekqVAUplmOyT/OuCQkogo6X98MtpSaJOKEAeZnYO8JB0U/RIEixZaGI5px73YbRm/oag==
|
integrity sha512-Qd2kQ3yi6Y3+AcUlrHxSLlnBvpdCEMVGFlVBneVOjaFaPU61g1huc38g339ysXspwY1QZA2aNhrk/KlHGO+ewA==
|
||||||
dependencies:
|
dependencies:
|
||||||
merge-options "^3.0.4"
|
merge-options "^3.0.4"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user