Merge branch 'main' into starter-packs

This commit is contained in:
Hailey
2024-06-04 21:18:13 -07:00
42 changed files with 17372 additions and 15415 deletions
+9 -2
View File
@@ -34,6 +34,9 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -96,13 +99,17 @@ jobs:
name: build-${{ steps.timestamp.outputs.time }}.apk
path: build.apk
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "Android build is ready for submission. This is a production build! Download the artifact here: ${{ steps.upload-artifact-production.outputs.artifact-url }}"
"text": "Android production build for Google Play Store submission is ready!\n```Artifact: ${{ steps.upload-artifact-production.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
@@ -140,7 +147,7 @@ jobs:
with:
payload: |
{
"text": "Android production APK build is ready for download. This is a production build, and you should add it to the GitHub release! Download the artifact here: ${{ steps.upload-artifact-production-apk.outputs.artifact-url }}"
"text": "Android production build for GitHub/Obtanium is ready!\n```Artifact: ${{ steps.upload-artifact-production-apk.outputs.artifact-url }}\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_ANDROID_VERSION_CODE }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
+19
View File
@@ -34,6 +34,9 @@ jobs:
node-version-file: .nvmrc
cache: yarn
- name: 🪛 Setup jq
uses: dcarbone/install-jq-action@v2
- name: 🔨 Setup EAS
uses: expo/expo-github-action@v8
with:
@@ -81,6 +84,22 @@ jobs:
- name: 🚀 Deploy
run: eas submit -p ios --non-interactive --path build.ipa
- name: 📚 Get version from package.json
id: get-build-info
run: bash scripts/setGitHubOutput.sh
- name: 🔔 Notify Slack of Production Build
if: ${{ inputs.profile == 'production' }}
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
"text": "iOS production build for App Store submission is ready!\n```Artifact: Check TestFlight to know when it is available\nVersion Number: ${{ steps.get-build-info.outputs.PACKAGE_VERSION }}\nBuild Number: ${{ steps.get-build-info.outputs.BSKY_IOS_BUILD_NUMBER }}```"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CLIENT_ALERT_WEBHOOK }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: ⬇️ Restore Cache
id: get-base-commit
uses: actions/cache@v4
+1 -1
View File
@@ -50,7 +50,7 @@ appId: xyz.blueskyweb.app
text: "1"
- tapOn:
id: "repostBtn"
- tapOn: "Undo repost"
- tapOn: "Remove repost"
- assertNotVisible:
id: "repostCount"
-31
View File
@@ -1,31 +0,0 @@
# Remove this test when the old onboarding is deprecated
appId: xyz.blueskyweb.app
---
- runScript:
file: ../setupServer.js
env:
SERVER_PATH: "?users"
- runFlow:
file: ../setupApp.yml
- tapOn:
id: "e2eSignInAlice"
- tapOn:
id: "e2eStartLongboarding"
- tapOn: "Continue to next step"
- tapOn: "Continue to the next step without following any accounts"
- tapOn: Show replies in Following feed
- tapOn: Show quote-posts in Following feed
- tapOn: Show re-posts in Following feed
- tapOn: Show replies in Following feed
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Continue to the next step"
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: Continue to next step
- waitForAnimationToEnd
- tapOn: "Complete onboarding and start using your account"
- waitForAnimationToEnd
- assertVisible: Following
+22 -8
View File
@@ -14,15 +14,20 @@ diff results in incompatible native changes, a new client build will automatical
### Prerequisites
- Remove any internal client from your device and download the client from the App Store/Google Play. This will help for
testing as well as retrieving the build number.
- You should have signed in to EAS locally through npx eas login. You will need to modify the build number in a
subsequent step.
- Identify the build number of the production app you want to deploy an update for. iOS and Android build numbers are
divergent, so you will need to find both
- Find the latest production build number for both iOS and Android in Slack. These are listed in #client-builds
- Production builds always send the Version Number and Build Number in the Slack message. Search for the latest
production version number, and you should find the correct information.
![slack-build-info](./img/slack-build-info.png)
- It may also be useful to check the current production clients for these values. This will also help for testing. Note
that you will need to _fully_ remove the existing internal client build from your device, otherwise the given values in
the app may differ from the actual production values.
![app-build-number](./img/app-build-number.png)
- You should have signed in to EAS locally through npx eas login. You will need to modify the build number in a
subsequent step.
- Ensure that the commit the initial client was cut from is properly tagged in git. The tag should be in the format of 1.X.0
- Note: If the commit is not properly tagged, then the OTA deployment will simply fail since the GitHub Action will
not be able to find a commit to fingerprint and diff against.
@@ -38,14 +43,17 @@ to create your branch from, this should be properly set.
### Deployment
- Update the build number through EAS
- Update the build number through EAS to match the build numbers of the
production iOS/Android apps
- Note: This isnt strictly necessary, but having a step that takes you off of GitHub and into the terminal provides
a little “friction” to avoid fat fingering a release. Since there are legitimate reasons to just “click and deploy”
for internal builds, I felt it useful to make sure it doesnt accidentally become a prod deployment.
- Set the build number to the appropriate build number found in the prerequisite steps. Again, this should be the
- Set the build numbers to the values found in the prerequisite steps. Again, this should be the
build number for the current production release you want to deploy for.
- `npx eas build:version:set -p ios`
- `npx eas build:version:set -p android`
- These steps should spit out what the current build number is, save those values
for later too
- Run the deployment
- Navigate to https://github.com/bluesky-social/social-app/actions/workflows/bundle-deploy-eas-update.yml
- Select the “Run Workflow” dropdown
@@ -79,3 +87,9 @@ In about five minutes, the new deployment should be available for download. To t
- Launch the app once and wait approximately 15 seconds
- Relaunch the app
- Check the Settings page and scroll to the bottom. The commit hash should now be the latest commit on your deployed branch.
### Post Deployment
- Reset both platforms build numbers to what they were before the OTA
deployment. These values should have been logged by the EAS CLI when you
reset them to the production values prior to OTA.
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

+1 -1
View File
@@ -49,7 +49,7 @@
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
},
"dependencies": {
"@atproto/api": "^0.12.14",
"@atproto/api": "^0.12.16",
"@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2",
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
outputIos=$(eas build:version:get -p ios)
outputAndroid=$(eas build:version:get -p android)
BSKY_IOS_BUILD_NUMBER=${outputIos#*buildNumber - }
BSKY_ANDROID_VERSION_CODE=${outputAndroid#*versionCode - }
echo PACKAGE_VERSION="$(jq -r '.version' package.json)" > "$GITHUB_OUTPUT"
echo BSKY_IOS_BUILD_NUMBER=$BSKY_IOS_BUILD_NUMBER >> "$GITHUB_OUTPUT"
echo BSKY_ANDROID_VERSION_CODE=$BSKY_ANDROID_VERSION_CODE >> "$GITHUB_OUTPUT"
@@ -16,6 +16,7 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useListConvosQuery} from '#/state/queries/messages/list-converations'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useSession} from '#/state/session'
import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete'
@@ -55,9 +56,11 @@ type Item =
export function SearchablePeopleList({
title,
onSelectChat,
showRecentConvos,
}: {
title: string
onSelectChat: (did: string) => void
showRecentConvos?: boolean
}) {
const t = useTheme()
const {_} = useLingui()
@@ -75,6 +78,7 @@ export function SearchablePeopleList({
isFetching,
} = useActorAutocompleteQuery(searchText, true, 12)
const {data: follows} = useProfileFollowsQuery(currentAccount?.did)
const {data: convos} = useListConvosQuery({enabled: showRecentConvos})
const items = useMemo(() => {
let _items: Item[] = []
@@ -103,7 +107,65 @@ export function SearchablePeopleList({
})
}
} else {
if (follows) {
const placeholders: Item[] = Array(10)
.fill(0)
.map((_, i) => ({
type: 'placeholder',
key: i + '',
}))
if (showRecentConvos) {
if (convos && follows) {
const usedDids = new Set()
for (const page of convos.pages) {
for (const convo of page.convos) {
const profiles = convo.members.filter(
m => m.did !== currentAccount?.did,
)
for (const profile of profiles) {
if (usedDids.has(profile.did)) continue
usedDids.add(profile.did)
_items.push({
type: 'profile',
key: profile.did,
enabled: true,
profile,
})
}
}
}
let followsItems: typeof _items = []
for (const page of follows.pages) {
for (const profile of page.follows) {
if (usedDids.has(profile.did)) continue
followsItems.push({
type: 'profile',
key: profile.did,
enabled: canBeMessaged(profile),
profile,
})
}
}
// only sort follows
followsItems = followsItems.sort(a => {
// @ts-ignore
return a.enabled ? -1 : 1
})
// then append
_items.push(...followsItems)
} else {
_items.push(...placeholders)
}
} else if (follows) {
for (const page of follows.pages) {
for (const profile of page.follows) {
_items.push({
@@ -120,19 +182,21 @@ export function SearchablePeopleList({
return a.enabled ? -1 : 1
})
} else {
Array(10)
.fill(0)
.forEach((_, i) => {
_items.push({
type: 'placeholder',
key: i + '',
})
})
_items.push(...placeholders)
}
}
return _items
}, [_, searchText, results, isError, currentAccount?.did, follows])
}, [
_,
searchText,
results,
isError,
currentAccount?.did,
follows,
convos,
showRecentConvos,
])
if (searchText && !isFetching && !items.length && !isError) {
items.push({type: 'empty', key: 'empty', message: _(msg`No results`)})
@@ -331,11 +395,13 @@ function ProfileCard({
/>
<View style={[a.flex_1, a.gap_2xs]}>
<Text
style={[t.atoms.text, a.font_bold, a.leading_snug]}
style={[t.atoms.text, a.font_bold, a.leading_tight]}
numberOfLines={1}>
{displayName}
</Text>
<Text style={t.atoms.text_contrast_high} numberOfLines={2}>
<Text
style={[a.leading_tight, t.atoms.text_contrast_high]}
numberOfLines={2}>
{!enabled ? <Trans>{handle} can't be messaged</Trans> : handle}
</Text>
</View>
@@ -46,6 +46,7 @@ export function SendViaChatDialog({
<SearchablePeopleList
title={_(msg`Send post to...`)}
onSelectChat={onCreateChat}
showRecentConvos
/>
</Dialog.Outer>
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+60 -3
View File
@@ -4,18 +4,27 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
import {
type SessionAccount,
useAgent,
useSession,
useSessionApi,
} from '#/state/session'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {ScrollView} from '#/view/com/util/Views'
import {Logo} from '#/view/icons/Logo'
import {atoms as a, useTheme} from '#/alf'
import {AccountList} from '#/components/AccountList'
import {Button, ButtonText} from '#/components/Button'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
const COL_WIDTH = 400
@@ -30,6 +39,10 @@ export function Deactivated() {
const hasOtherAccounts = accounts.length > 1
const setMinimalShellMode = useSetMinimalShellMode()
const {logout} = useSessionApi()
const agent = useAgent()
const [pending, setPending] = React.useState(false)
const [error, setError] = React.useState<string | undefined>()
const queryClient = useQueryClient()
useFocusEffect(
React.useCallback(() => {
@@ -62,6 +75,34 @@ export function Deactivated() {
logout('Deactivated')
}, [logout])
const handleActivate = React.useCallback(async () => {
try {
setPending(true)
await agent.com.atproto.server.activateAccount()
await queryClient.resetQueries()
await agent.resumeSession(agent.session!)
} catch (e: any) {
switch (e.message) {
case 'Bad token scope':
setError(
_(
msg`You're logged in with an App Password. Please log in with your main password to continue deactivating your account.`,
),
)
break
default:
setError(_(msg`Something went wrong, please try again`))
break
}
logger.error(e, {
context: 'Failed to activate account',
})
} finally {
setPending(false)
}
}, [_, agent, setPending, setError, queryClient])
return (
<View style={[a.h_full_vh, a.flex_1, t.atoms.bg]}>
<ScrollView
@@ -104,10 +145,11 @@ export function Deactivated() {
size="medium"
variant="solid"
color="primary"
onPress={() => setShowLoggedOut(true)}>
onPress={handleActivate}>
<ButtonText>
<Trans>Yes, reactivate my account</Trans>
</ButtonText>
{pending && <ButtonIcon icon={Loader} position="right" />}
</Button>
<Button
label={_(msg`Cancel reactivation and log out`)}
@@ -120,6 +162,21 @@ export function Deactivated() {
</ButtonText>
</Button>
</View>
{error && (
<View
style={[
a.flex_row,
a.gap_sm,
a.mt_md,
a.p_md,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo size="md" fill={t.palette.negative_400} />
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</View>
)}
</View>
<View style={[a.pb_3xl]}>
+11 -9
View File
@@ -81,15 +81,17 @@ let ProfileHeaderShell = ({
{children}
<View
style={[a.px_lg, a.pb_sm]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
{isMe ? (
<LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
) : (
<ProfileHeaderAlerts moderation={moderation} />
)}
</View>
{!isPlaceholderProfile && (
<View
style={[a.px_lg, a.pb_sm]}
pointerEvents={isIOS ? 'auto' : 'box-none'}>
{isMe ? (
<LabelsOnMe details={{did: profile.did}} labels={profile.labels} />
) : (
<ProfileHeaderAlerts moderation={moderation} />
)}
</View>
)}
{!isDesktop && !hideBackButton && (
<TouchableWithoutFeedback
@@ -3,9 +3,14 @@ import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {atoms as a, useTheme} from '#/alf'
import {logger} from '#/logger'
import {useAgent, useSessionApi} from '#/state/session'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {DialogOuterProps} from '#/components/Dialog'
import {Divider} from '#/components/Divider'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
import {Loader} from '#/components/Loader'
import * as Prompt from '#/components/Prompt'
import {Text} from '#/components/Typography'
@@ -13,12 +18,58 @@ export function DeactivateAccountDialog({
control,
}: {
control: DialogOuterProps['control']
}) {
return (
<Prompt.Outer control={control}>
<DeactivateAccountDialogInner control={control} />
</Prompt.Outer>
)
}
function DeactivateAccountDialogInner({
control,
}: {
control: DialogOuterProps['control']
}) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {_} = useLingui()
const agent = useAgent()
const {logout} = useSessionApi()
const [pending, setPending] = React.useState(false)
const [error, setError] = React.useState<string | undefined>()
const handleDeactivate = React.useCallback(async () => {
try {
setPending(true)
await agent.com.atproto.server.deactivateAccount({})
control.close(() => {
logout('Deactivated')
})
} catch (e: any) {
switch (e.message) {
case 'Bad token scope':
setError(
_(
msg`You're logged in with an App Password. Please log in with your main password to continue deactivating your account.`,
),
)
break
default:
setError(_(msg`Something went wrong, please try again`))
break
}
logger.error(e, {
context: 'Failed to deactivate account',
})
} finally {
setPending(false)
}
}, [agent, control, logout, _, setPending])
return (
<Prompt.Outer control={control} testID="confirmModal">
<>
<Prompt.TitleText>{_(msg`Deactivate account`)}</Prompt.TitleText>
<Prompt.DescriptionText>
<Trans>
@@ -48,13 +99,32 @@ export function DeactivateAccountDialog({
<Divider />
</View>
<Prompt.Actions>
<Prompt.Action
cta={_(msg`Yes, deactivate`)}
onPress={() => {}}
<Button
variant="solid"
color="negative"
/>
size={gtMobile ? 'small' : 'medium'}
label={_(msg`Yes, deactivate`)}
onPress={handleDeactivate}>
<ButtonText>{_(msg`Yes, deactivate`)}</ButtonText>
{pending && <ButtonIcon icon={Loader} position="right" />}
</Button>
<Prompt.Cancel />
</Prompt.Actions>
</Prompt.Outer>
{error && (
<View
style={[
a.flex_row,
a.gap_sm,
a.mt_md,
a.p_md,
a.rounded_sm,
t.atoms.bg_contrast_25,
]}>
<CircleInfo size="md" fill={t.palette.negative_400} />
<Text style={[a.flex_1, a.leading_snug]}>{error}</Text>
</View>
)}
</>
)
}
+6 -3
View File
@@ -18,9 +18,12 @@ const accountSchema = z.object({
refreshJwt: z.string().optional(), // optional because it can expire
accessJwt: z.string().optional(), // optional because it can expire
signupQueued: z.boolean().optional(),
status: z
.enum(['active', 'takendown', 'suspended', 'deactivated'])
.optional(),
active: z.boolean().optional(), // optional for backwards compat
/**
* Known values: takendown, suspended, deactivated
* @see https://github.com/bluesky-social/atproto/blob/5441fbde9ed3b22463e91481ec80cb095643e141/lexicons/com/atproto/server/getSession.json
*/
status: z.string().optional(),
pdsUrl: z.string().optional(),
})
export type PersistedAccount = z.infer<typeof accountSchema>
+13 -2
View File
@@ -1,7 +1,7 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import {Schema, schema} from '#/state/persisted/schema'
import {logger} from '#/logger'
import {Schema, schema} from '#/state/persisted/schema'
const BSKY_STORAGE = 'BSKY_STORAGE'
@@ -13,8 +13,19 @@ export async function write(value: Schema) {
export async function read(): Promise<Schema | undefined> {
const rawData = await AsyncStorage.getItem(BSKY_STORAGE)
const objData = rawData ? JSON.parse(rawData) : undefined
if (schema.safeParse(objData).success) {
const parsed = schema.safeParse(objData)
if (parsed.success) {
return objData
} else {
const errors =
parsed.error?.errors?.map(e => ({
code: e.code,
// @ts-ignore exists on some types
expected: e?.expected,
path: e.path,
})) || []
logger.error(`persisted store: data failed validation on read`, {errors})
return undefined
}
}
@@ -26,10 +26,15 @@ import {useAgent, useSession} from '#/state/session'
export const RQKEY = ['convo-list']
type RQPageParam = string | undefined
export function useListConvosQuery() {
export function useListConvosQuery({
enabled,
}: {
enabled?: boolean
} = {}) {
const agent = useAgent()
return useInfiniteQuery({
enabled,
queryKey: RQKEY,
queryFn: async ({pageParam}) => {
const {data} = await agent.api.chat.bsky.convo.listConvos(
+90 -29
View File
@@ -28,6 +28,7 @@ describe('session', () => {
const agent = new BskyAgent({service: 'https://alice.com'})
agent.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -50,6 +51,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-1",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -59,7 +61,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-1",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -88,6 +90,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": undefined,
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -97,7 +100,7 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -116,6 +119,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -138,6 +142,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-1",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -147,7 +152,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-1",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -162,6 +167,7 @@ describe('session', () => {
const agent2 = new BskyAgent({service: 'https://bob.com'})
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-1',
@@ -186,6 +192,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "bob-access-jwt-1",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -195,10 +202,11 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-1",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "alice-access-jwt-1",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -208,7 +216,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-1",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -223,6 +231,7 @@ describe('session', () => {
const agent3 = new BskyAgent({service: 'https://alice.com'})
agent3.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-2',
@@ -247,6 +256,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -256,10 +266,11 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "bob-access-jwt-1",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -269,7 +280,7 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-1",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -284,6 +295,7 @@ describe('session', () => {
const agent4 = new BskyAgent({service: 'https://jay.com'})
agent4.session = {
active: true,
did: 'jay-did',
handle: 'jay.test',
accessJwt: 'jay-access-jwt-1',
@@ -306,6 +318,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "jay-access-jwt-1",
"active": true,
"did": "jay-did",
"email": undefined,
"emailAuthFactor": false,
@@ -315,10 +328,11 @@ describe('session', () => {
"refreshJwt": "jay-refresh-jwt-1",
"service": "https://jay.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -328,10 +342,11 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "bob-access-jwt-1",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -341,7 +356,7 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-1",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -374,6 +389,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": undefined,
"active": true,
"did": "jay-did",
"email": undefined,
"emailAuthFactor": false,
@@ -383,10 +399,11 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://jay.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": undefined,
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -396,10 +413,11 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": undefined,
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -409,7 +427,7 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -428,6 +446,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -459,6 +478,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": undefined,
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -468,7 +488,7 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -483,6 +503,7 @@ describe('session', () => {
const agent2 = new BskyAgent({service: 'https://alice.com'})
agent2.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-2',
@@ -504,6 +525,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -513,7 +535,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -532,6 +554,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -576,6 +599,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -583,6 +607,7 @@ describe('session', () => {
}
const agent2 = new BskyAgent({service: 'https://bob.com'})
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-1',
@@ -616,6 +641,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "bob-access-jwt-1",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -625,7 +651,7 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-1",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -653,6 +679,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -669,6 +696,7 @@ describe('session', () => {
expect(state.currentAgentState.did).toBe('alice-did')
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-2',
@@ -697,6 +725,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": "alice@foo.bar",
"emailAuthFactor": false,
@@ -706,7 +735,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -720,6 +749,7 @@ describe('session', () => {
`)
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-3',
@@ -748,6 +778,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-3",
"active": true,
"did": "alice-did",
"email": "alice@foo.baz",
"emailAuthFactor": true,
@@ -757,7 +788,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-3",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -771,6 +802,7 @@ describe('session', () => {
`)
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-4',
@@ -799,6 +831,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-4",
"active": true,
"did": "alice-did",
"email": "alice@foo.baz",
"emailAuthFactor": false,
@@ -808,7 +841,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-4",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -827,6 +860,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -843,6 +877,7 @@ describe('session', () => {
expect(state.currentAgentState.did).toBe('alice-did')
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-2',
@@ -873,6 +908,7 @@ describe('session', () => {
expect(lastState === state).toBe(true)
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-3',
@@ -896,6 +932,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -904,6 +941,7 @@ describe('session', () => {
const agent2 = new BskyAgent({service: 'https://bob.com'})
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-1',
@@ -928,6 +966,7 @@ describe('session', () => {
expect(state.currentAgentState.did).toBe('bob-did')
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice-updated.test',
accessJwt: 'alice-access-jwt-2',
@@ -956,6 +995,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "bob-access-jwt-1",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -965,10 +1005,11 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-1",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": "alice@foo.bar",
"emailAuthFactor": false,
@@ -978,7 +1019,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -992,6 +1033,7 @@ describe('session', () => {
`)
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob-updated.test',
accessJwt: 'bob-access-jwt-2',
@@ -1018,6 +1060,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "bob-access-jwt-2",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1027,10 +1070,11 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-2",
"service": "https://bob.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "alice-access-jwt-2",
"active": true,
"did": "alice-did",
"email": "alice@foo.bar",
"emailAuthFactor": false,
@@ -1040,7 +1084,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -1083,6 +1127,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -1091,6 +1136,7 @@ describe('session', () => {
const agent2 = new BskyAgent({service: 'https://bob.com'})
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-1',
@@ -1117,6 +1163,7 @@ describe('session', () => {
expect(state.currentAgentState.did).toBe('bob-did')
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-2',
@@ -1142,6 +1189,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -1179,6 +1227,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "alice-access-jwt-1",
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1188,7 +1237,7 @@ describe('session', () => {
"refreshJwt": "alice-refresh-jwt-1",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -1207,6 +1256,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -1242,6 +1292,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": undefined,
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1251,7 +1302,7 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -1270,6 +1321,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -1305,6 +1357,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": undefined,
"active": true,
"did": "alice-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1314,7 +1367,7 @@ describe('session', () => {
"refreshJwt": undefined,
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -1333,6 +1386,7 @@ describe('session', () => {
const agent1 = new BskyAgent({service: 'https://alice.com'})
agent1.session = {
active: true,
did: 'alice-did',
handle: 'alice.test',
accessJwt: 'alice-access-jwt-1',
@@ -1340,6 +1394,7 @@ describe('session', () => {
}
const agent2 = new BskyAgent({service: 'https://bob.com'})
agent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-1',
@@ -1362,6 +1417,7 @@ describe('session', () => {
const anotherTabAgent1 = new BskyAgent({service: 'https://jay.com'})
anotherTabAgent1.session = {
active: true,
did: 'jay-did',
handle: 'jay.test',
accessJwt: 'jay-access-jwt-1',
@@ -1369,6 +1425,7 @@ describe('session', () => {
}
const anotherTabAgent2 = new BskyAgent({service: 'https://alice.com'})
anotherTabAgent2.session = {
active: true,
did: 'bob-did',
handle: 'bob.test',
accessJwt: 'bob-access-jwt-2',
@@ -1397,6 +1454,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "jay-access-jwt-1",
"active": true,
"did": "jay-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1406,10 +1464,11 @@ describe('session', () => {
"refreshJwt": "jay-refresh-jwt-1",
"service": "https://jay.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
{
"accessJwt": "bob-access-jwt-2",
"active": true,
"did": "bob-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1419,7 +1478,7 @@ describe('session', () => {
"refreshJwt": "bob-refresh-jwt-2",
"service": "https://alice.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
@@ -1434,6 +1493,7 @@ describe('session', () => {
const anotherTabAgent3 = new BskyAgent({service: 'https://clarence.com'})
anotherTabAgent3.session = {
active: true,
did: 'clarence-did',
handle: 'clarence.test',
accessJwt: 'clarence-access-jwt-2',
@@ -1457,6 +1517,7 @@ describe('session', () => {
"accounts": [
{
"accessJwt": "clarence-access-jwt-2",
"active": true,
"did": "clarence-did",
"email": undefined,
"emailAuthFactor": false,
@@ -1466,7 +1527,7 @@ describe('session', () => {
"refreshJwt": "clarence-refresh-jwt-2",
"service": "https://clarence.com/",
"signupQueued": false,
"status": "active",
"status": undefined,
},
],
"currentAgentState": {
+7 -2
View File
@@ -46,6 +46,11 @@ export async function createAgentAndResume(
emailConfirmed: storedAccount.emailConfirmed,
handle: storedAccount.handle,
refreshJwt: storedAccount.refreshJwt ?? '',
/**
* @see https://github.com/bluesky-social/atproto/blob/c5d36d5ba2a2c2a5c4f366a5621c06a5608e361e/packages/api/src/agent.ts#L188
*/
active: storedAccount.active ?? true,
status: storedAccount.status,
}
if (isSessionExpired(storedAccount)) {
await networkRetry(1, () => agent.resumeSession(prevSession))
@@ -235,8 +240,8 @@ export function agentToSessionAccount(
refreshJwt: agent.session.refreshJwt,
accessJwt: agent.session.accessJwt,
signupQueued: isSignupQueued(agent.session.accessJwt),
// @ts-expect-error TODO remove when backend is ready
status: agent.session.status || 'active',
active: agent.session.active,
status: agent.session.status as SessionAccount['status'],
pdsUrl: agent.pdsUrl?.toString(),
}
}
+8 -1
View File
@@ -154,6 +154,13 @@ export const ComposePost = observer(function ComposePost({
const [extGif, setExtGif] = useState<Gif>()
const [labels, setLabels] = useState<string[]>([])
const [threadgate, setThreadgate] = useState<ThreadgateSetting[]>([])
React.useEffect(() => {
if (!isAndroid) return
const id = setTimeout(() => textInput.current?.focus(), 100)
return () => clearTimeout(id)
}, [])
const gallery = useMemo(
() => new GalleryModel(initImageUris),
[initImageUris],
@@ -517,7 +524,7 @@ export const ComposePost = observer(function ComposePost({
ref={textInput}
richtext={richtext}
placeholder={selectTextInputPlaceholder}
autoFocus={true}
autoFocus={!isAndroid}
setRichText={setRichText}
onPhotoPasted={onPhotoPasted}
onPressPublish={onPressPublish}
+6 -1
View File
@@ -161,7 +161,11 @@ export function Feed({
)
} else if (item === LOADING_ITEM) {
return (
<View style={[pal.border, {borderTopWidth: hairlineWidth}]}>
<View
style={[
pal.border,
!isTabletOrMobile && {borderTopWidth: hairlineWidth},
]}>
<NotificationFeedLoadingPlaceholder />
</View>
)
@@ -217,6 +221,7 @@ export function Feed({
desktopFixedHeight
initialNumToRender={initialNumToRender}
windowSize={11}
sideBorders={false}
/>
</View>
)
@@ -108,6 +108,7 @@ let RepostButton = ({
</Text>
</Button>
<Button
testID="quoteBtn"
style={[a.justify_start, a.px_md]}
label={_(msg`Quote post`)}
onPress={() => {
+4 -2
View File
@@ -181,9 +181,11 @@ export function QuoteEmbed({
}, [queryClient, quote.author, onOpen])
return (
<ContentHider modui={moderation?.ui('contentList')}>
<ContentHider
modui={moderation?.ui('contentList')}
style={[styles.container, pal.borderDark, style]}
childContainerStyle={[a.pt_sm]}>
<Link
style={[styles.container, pal.borderDark, style]}
hoverStyle={{borderColor: pal.colors.borderLinkHover}}
href={itemHref}
title={itemTitle}
+6 -2
View File
@@ -29,6 +29,7 @@ import {colors, s} from 'lib/styles'
import {TextLink} from 'view/com/util/Link'
import {ListMethods} from 'view/com/util/List'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {CenteredView} from 'view/com/util/Views'
import {Feed} from '../com/notifications/Feed'
import {FAB} from '../com/util/fab/FAB'
import {MainScrollProvider} from '../com/util/MainScrollProvider'
@@ -145,7 +146,10 @@ export function NotificationsScreen({}: Props) {
}, [isDesktop, pal, hasNew])
return (
<View testID="notificationsScreen" style={s.hContentRegion}>
<CenteredView
testID="notificationsScreen"
style={s.hContentRegion}
sideBorders={true}>
<ViewHeader
title={_(msg`Notifications`)}
canGoBack={false}
@@ -173,6 +177,6 @@ export function NotificationsScreen({}: Props) {
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
</CenteredView>
)
}
+7 -8
View File
@@ -32,6 +32,7 @@ import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
import {atoms as a, useTheme} from '#/alf'
import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
import hairlineWidth = StyleSheet.hairlineWidth
const HITSLOP_TOP = {
top: 20,
@@ -92,7 +93,7 @@ export function SavedFeeds({}: Props) {
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
<ScrollView style={s.flex1} contentContainerStyle={[styles.noBorder]}>
{noSavedFeedsOfAnyType && (
<View style={[pal.border, {borderBottomWidth: 1}]}>
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
<NoSavedFeedsOfAnyType />
</View>
)}
@@ -134,7 +135,7 @@ export function SavedFeeds({}: Props) {
)}
{noFollowingFeed && (
<View style={[pal.border, {borderBottomWidth: 1}]}>
<View style={[pal.border, {borderBottomWidth: hairlineWidth}]}>
<NoFollowingFeed />
</View>
)}
@@ -298,9 +299,10 @@ function ListItem({
<FeedSourceCard
key={feedUri}
feedUri={feedUri}
style={[styles.noTopBorder, isPinned && {paddingRight: 8}]}
style={[isPinned && {paddingRight: 8}]}
showMinimalPlaceholder
showSaveBtn={!isPinned}
hideTopBorder={true}
/>
)}
{isPinned ? (
@@ -435,15 +437,12 @@ const styles = StyleSheet.create({
paddingHorizontal: 14,
paddingTop: 20,
paddingBottom: 10,
borderBottomWidth: 1,
borderBottomWidth: hairlineWidth,
},
itemContainer: {
flexDirection: 'row',
alignItems: 'center',
borderBottomWidth: 1,
},
noTopBorder: {
borderTopWidth: 0,
borderBottomWidth: hairlineWidth,
},
footerText: {
paddingHorizontal: 26,
+4 -4
View File
@@ -34,10 +34,10 @@
jsonpointer "^5.0.0"
leven "^3.1.0"
"@atproto/api@^0.12.14":
version "0.12.14"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.14.tgz#81252fd166ec8fe950056531e690d563437720fa"
integrity sha512-ZPh/afoRjFEQDQgMZW2FQiG5CDUifY7SxBqI0zVJUwed8Zi6fqYzGYM8fcDvD8yJfflRCqRxUE72g5fKiA1zAQ==
"@atproto/api@^0.12.16":
version "0.12.16"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.16.tgz#f5b5e06d75d379dafe79521d727ed8ad5516d3fc"
integrity sha512-v3lA/m17nkawDXiqgwXyaUSzJPeXJBMH8QKOoYxcDqN+8yG9LFlGe2ecGarXcbGQjYT0GJTAAW3Y/AaCOEwuLg==
dependencies:
"@atproto/common-web" "^0.3.0"
"@atproto/lexicon" "^0.4.0"