Merge branch 'main' into starter-packs
This commit is contained in:
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -50,7 +50,7 @@ appId: xyz.blueskyweb.app
|
||||
text: "1"
|
||||
- tapOn:
|
||||
id: "repostBtn"
|
||||
- tapOn: "Undo repost"
|
||||
- tapOn: "Remove repost"
|
||||
- assertNotVisible:
|
||||
id: "repostCount"
|
||||
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||

|
||||
|
||||
- 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.
|
||||
|
||||

|
||||
|
||||
- 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 isn’t 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 doesn’t 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
@@ -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",
|
||||
|
||||
Executable
+9
@@ -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>
|
||||
)
|
||||
|
||||
+1001
-844
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+999
-842
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+949
-992
File diff suppressed because it is too large
Load Diff
+1200
-1178
File diff suppressed because it is too large
Load Diff
+1000
-843
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+926
-956
File diff suppressed because it is too large
Load Diff
+914
-926
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+1001
-844
File diff suppressed because it is too large
Load Diff
+1002
-845
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1007
-1019
File diff suppressed because it is too large
Load Diff
@@ -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]}>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user