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
@@ -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,