Merge remote-tracking branch 'upstream/main' into Improve-notification-localization
This commit is contained in:
@@ -132,7 +132,8 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "feedItem-by-bob.test"
|
id: "feedItem-by-bob.test"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "e2eGotoFeeds"
|
id: "e2eGotoFeeds"
|
||||||
- tapOn: "Good Ppl"
|
- tapOn:
|
||||||
|
id: "saved-feed-Good Ppl"
|
||||||
- assertVisible:
|
- assertVisible:
|
||||||
id: "feedItem-by-bob.test"
|
id: "feedItem-by-bob.test"
|
||||||
- tapOn:
|
- tapOn:
|
||||||
@@ -168,7 +169,7 @@ appId: xyz.blueskyweb.app
|
|||||||
id: "profilePager-selector"
|
id: "profilePager-selector"
|
||||||
direction: LEFT
|
direction: LEFT
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "profilePager-selector-5"
|
id: "profilePager-selector-6"
|
||||||
- tapOn: "Good Ppl"
|
- tapOn: "Good Ppl"
|
||||||
|
|
||||||
- tapOn:
|
- tapOn:
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ func serve(cctx *cli.Context) error {
|
|||||||
e.GET("/search", server.WebGeneric)
|
e.GET("/search", server.WebGeneric)
|
||||||
e.GET("/feeds", server.WebGeneric)
|
e.GET("/feeds", server.WebGeneric)
|
||||||
e.GET("/notifications", server.WebGeneric)
|
e.GET("/notifications", server.WebGeneric)
|
||||||
|
e.GET("/notifications/settings", server.WebGeneric)
|
||||||
e.GET("/lists", server.WebGeneric)
|
e.GET("/lists", server.WebGeneric)
|
||||||
e.GET("/moderation", server.WebGeneric)
|
e.GET("/moderation", server.WebGeneric)
|
||||||
e.GET("/moderation/modlists", server.WebGeneric)
|
e.GET("/moderation/modlists", server.WebGeneric)
|
||||||
|
|||||||
+1
-1
@@ -52,7 +52,7 @@
|
|||||||
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
|
"open-analyzer": "EXPO_PUBLIC_OPEN_ANALYZER=1 yarn build-web"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@atproto/api": "^0.12.23",
|
"@atproto/api": "0.12.25",
|
||||||
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
"@bam.tech/react-native-image-resizer": "^3.0.4",
|
||||||
"@braintree/sanitize-url": "^6.0.2",
|
"@braintree/sanitize-url": "^6.0.2",
|
||||||
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
"@discord/bottom-sheet": "bluesky-social/react-native-bottom-sheet",
|
||||||
|
|||||||
+4
-1
@@ -95,7 +95,10 @@ function InnerApp() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return listenSessionDropped(() => {
|
return listenSessionDropped(() => {
|
||||||
Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
|
Toast.show(
|
||||||
|
_(msg`Sorry! Your session expired. Please log in again.`),
|
||||||
|
'info',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -77,7 +77,10 @@ function InnerApp() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return listenSessionDropped(() => {
|
return listenSessionDropped(() => {
|
||||||
Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
|
Toast.show(
|
||||||
|
_(msg`Sorry! Your session expired. Please log in again.`),
|
||||||
|
'info',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}, [_])
|
}, [_])
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ import {LogScreen} from './view/screens/Log'
|
|||||||
import {ModerationModlistsScreen} from './view/screens/ModerationModlists'
|
import {ModerationModlistsScreen} from './view/screens/ModerationModlists'
|
||||||
import {NotFoundScreen} from './view/screens/NotFound'
|
import {NotFoundScreen} from './view/screens/NotFound'
|
||||||
import {NotificationsScreen} from './view/screens/Notifications'
|
import {NotificationsScreen} from './view/screens/Notifications'
|
||||||
|
import {NotificationsSettingsScreen} from './view/screens/NotificationsSettings'
|
||||||
import {PostLikedByScreen} from './view/screens/PostLikedBy'
|
import {PostLikedByScreen} from './view/screens/PostLikedBy'
|
||||||
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
|
import {PostRepostedByScreen} from './view/screens/PostRepostedBy'
|
||||||
import {PostThreadScreen} from './view/screens/PostThread'
|
import {PostThreadScreen} from './view/screens/PostThread'
|
||||||
@@ -324,6 +325,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
|
|||||||
getComponent={() => MessagesSettingsScreen}
|
getComponent={() => MessagesSettingsScreen}
|
||||||
options={{title: title(msg`Chat settings`), requireAuth: true}}
|
options={{title: title(msg`Chat settings`), requireAuth: true}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="NotificationsSettings"
|
||||||
|
getComponent={() => NotificationsSettingsScreen}
|
||||||
|
options={{title: title(msg`Notification settings`), requireAuth: true}}
|
||||||
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="Feeds"
|
name="Feeds"
|
||||||
getComponent={() => FeedsScreen}
|
getComponent={() => FeedsScreen}
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ export const atoms = {
|
|||||||
flex_shrink: {
|
flex_shrink: {
|
||||||
flexShrink: 1,
|
flexShrink: 1,
|
||||||
},
|
},
|
||||||
|
flex_shrink_0: {
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
justify_start: {
|
justify_start: {
|
||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -264,9 +264,9 @@ function SaveButtonInner({
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
Toast.show(_(msg`Feeds updated!`))
|
Toast.show(_(msg`Feeds updated!`))
|
||||||
} catch (e: any) {
|
} catch (err: any) {
|
||||||
logger.error(e, {context: `FeedCard: failed to update feeds`, pin})
|
logger.error(err, {context: `FeedCard: failed to update feeds`, pin})
|
||||||
Toast.show(_(msg`Failed to update feeds`))
|
Toast.show(_(msg`Failed to update feeds`), 'xmark')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type],
|
[_, pin, saveFeeds, removeFeed, uri, savedFeedConfig, type],
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ let ListMaybePlaceholder = ({
|
|||||||
return (
|
return (
|
||||||
<Error
|
<Error
|
||||||
title={errorTitle ?? _(msg`Oops!`)}
|
title={errorTitle ?? _(msg`Oops!`)}
|
||||||
message={errorMessage ?? _(`Something went wrong!`)}
|
message={errorMessage ?? _(msg`Something went wrong!`)}
|
||||||
onRetry={onRetry}
|
onRetry={onRetry}
|
||||||
onGoBack={onGoBack}
|
onGoBack={onGoBack}
|
||||||
sideBorders={sideBorders}
|
sideBorders={sideBorders}
|
||||||
|
|||||||
@@ -306,9 +306,9 @@ export function FollowButtonInner({
|
|||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (err: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -326,9 +326,9 @@ export function FollowButtonInner({
|
|||||||
)}`,
|
)}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} catch (e: any) {
|
} catch (err: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (err?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,10 @@ export function QrCodeDialog({
|
|||||||
try {
|
try {
|
||||||
await createAssetAsync(`file://${uri}`)
|
await createAssetAsync(`file://${uri}`)
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
Toast.show(_(msg`An error occurred while saving the QR code!`))
|
Toast.show(
|
||||||
|
_(msg`An error occurred while saving the QR code!`),
|
||||||
|
'xmark',
|
||||||
|
)
|
||||||
logger.error('Failed to save QR code', {error: e})
|
logger.error('Failed to save QR code', {error: e})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ function ShareDialogInner({
|
|||||||
if (!res) {
|
if (!res) {
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(msg`You must grant access to your photo library to save the image.`),
|
_(msg`You must grant access to your photo library to save the image.`),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -75,7 +76,7 @@ function ShareDialogInner({
|
|||||||
Toast.show(_(msg`Image saved to your camera roll!`))
|
Toast.show(_(msg`Image saved to your camera roll!`))
|
||||||
control.close()
|
control.close()
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
Toast.show(_(msg`An error occurred while saving the QR code!`))
|
Toast.show(_(msg`An error occurred while saving the QR code!`), 'xmark')
|
||||||
logger.error('Failed to save QR code', {error: e})
|
logger.error('Failed to save QR code', {error: e})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) {
|
|||||||
_(
|
_(
|
||||||
msg`There was an issue. Please check your internet connection and try again.`,
|
msg`There was an issue. Please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
logger.error('Failed to edit threadgate', {message: err})
|
logger.error('Failed to edit threadgate', {message: err})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ let ConvoMenu = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.show(_(msg`Could not mute chat`))
|
Toast.show(_(msg`Could not mute chat`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function LeaveConvoPrompt({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.show(_(msg`Could not leave chat`))
|
Toast.show(_(msg`Could not leave chat`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export let MessageMenu = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
Clipboard.setStringAsync(str)
|
Clipboard.setStringAsync(str)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
}, [_, message.text, message.facets])
|
}, [_, message.text, message.facets])
|
||||||
|
|
||||||
const onPressTranslateMessage = React.useCallback(() => {
|
const onPressTranslateMessage = React.useCallback(() => {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ function DialogInner({
|
|||||||
const [initialized, setInitialzed] = React.useState(false)
|
const [initialized, setInitialzed] = React.useState(false)
|
||||||
const {mutate: updateDeclaration} = useUpdateActorDeclaration({
|
const {mutate: updateDeclaration} = useUpdateActorDeclaration({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.show(_(msg`Failed to update settings`))
|
Toast.show(_(msg`Failed to update settings`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, {useCallback} from 'react'
|
|||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||||
import {logEvent} from 'lib/statsig/statsig'
|
import {logEvent} from 'lib/statsig/statsig'
|
||||||
import {FAB} from '#/view/com/util/fab/FAB'
|
import {FAB} from '#/view/com/util/fab/FAB'
|
||||||
@@ -31,7 +32,8 @@ export function NewChat({
|
|||||||
logEvent('chat:open', {logContext: 'NewChatDialog'})
|
logEvent('chat:open', {logContext: 'NewChatDialog'})
|
||||||
},
|
},
|
||||||
onError: error => {
|
onError: error => {
|
||||||
Toast.show(error.message)
|
logger.error('Failed to create chat', {safeMessage: error})
|
||||||
|
Toast.show(_(msg`An issue occurred starting the chat`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React, {useCallback} from 'react'
|
|||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {logger} from '#/logger'
|
||||||
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members'
|
||||||
import {logEvent} from 'lib/statsig/statsig'
|
import {logEvent} from 'lib/statsig/statsig'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
@@ -43,7 +44,11 @@ function SendViaChatDialogInner({
|
|||||||
logEvent('chat:open', {logContext: 'SendViaChatDialog'})
|
logEvent('chat:open', {logContext: 'SendViaChatDialog'})
|
||||||
},
|
},
|
||||||
onError: error => {
|
onError: error => {
|
||||||
Toast.show(error.message)
|
logger.error('Failed to share post to chat', {message: error})
|
||||||
|
Toast.show(
|
||||||
|
_(msg`An issue occurred while trying to open the chat`),
|
||||||
|
'xmark',
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Pressable, View, ViewStyle} from 'react-native'
|
import {Pressable, View, ViewStyle} from 'react-native'
|
||||||
|
import Animated, {LinearTransition} from 'react-native-reanimated'
|
||||||
|
|
||||||
|
import {isNative} from '#/platform/detection'
|
||||||
import {HITSLOP_10} from 'lib/constants'
|
import {HITSLOP_10} from 'lib/constants'
|
||||||
import {
|
import {
|
||||||
atoms as a,
|
atoms as a,
|
||||||
@@ -244,6 +246,7 @@ export function LabelText({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
a.font_bold,
|
a.font_bold,
|
||||||
|
a.leading_tight,
|
||||||
{
|
{
|
||||||
userSelect: 'none',
|
userSelect: 'none',
|
||||||
color: disabled
|
color: disabled
|
||||||
@@ -251,7 +254,7 @@ export function LabelText({
|
|||||||
: t.atoms.text_contrast_high.color,
|
: t.atoms.text_contrast_high.color,
|
||||||
},
|
},
|
||||||
native({
|
native({
|
||||||
paddingTop: 3,
|
paddingTop: 2,
|
||||||
}),
|
}),
|
||||||
flatten(style),
|
flatten(style),
|
||||||
]}>
|
]}>
|
||||||
@@ -344,10 +347,10 @@ export function Checkbox() {
|
|||||||
style={[
|
style={[
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.border,
|
|
||||||
a.rounded_xs,
|
a.rounded_xs,
|
||||||
t.atoms.border_contrast_high,
|
t.atoms.border_contrast_high,
|
||||||
{
|
{
|
||||||
|
borderWidth: 1,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
},
|
},
|
||||||
@@ -375,34 +378,35 @@ export function Switch() {
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
a.relative,
|
a.relative,
|
||||||
a.border,
|
|
||||||
a.rounded_full,
|
a.rounded_full,
|
||||||
t.atoms.bg,
|
t.atoms.bg,
|
||||||
t.atoms.border_contrast_high,
|
t.atoms.border_contrast_high,
|
||||||
{
|
{
|
||||||
|
borderWidth: 1,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 30,
|
width: 32,
|
||||||
|
padding: 2,
|
||||||
},
|
},
|
||||||
baseStyles,
|
baseStyles,
|
||||||
hovered ? baseHoverStyles : {},
|
hovered ? baseHoverStyles : {},
|
||||||
]}>
|
]}>
|
||||||
<View
|
<Animated.View
|
||||||
|
layout={LinearTransition.duration(100)}
|
||||||
style={[
|
style={[
|
||||||
a.absolute,
|
|
||||||
a.rounded_full,
|
a.rounded_full,
|
||||||
{
|
{
|
||||||
height: 12,
|
height: 14,
|
||||||
width: 12,
|
width: 14,
|
||||||
top: 3,
|
|
||||||
left: 3,
|
|
||||||
backgroundColor: t.palette.contrast_400,
|
|
||||||
},
|
},
|
||||||
selected
|
selected
|
||||||
? {
|
? {
|
||||||
backgroundColor: t.palette.primary_500,
|
backgroundColor: t.palette.primary_500,
|
||||||
left: 13,
|
alignSelf: 'flex-end',
|
||||||
}
|
}
|
||||||
: {},
|
: {
|
||||||
|
backgroundColor: t.palette.contrast_400,
|
||||||
|
alignSelf: 'flex-start',
|
||||||
|
},
|
||||||
indicatorStyles,
|
indicatorStyles,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -428,10 +432,10 @@ export function Radio() {
|
|||||||
style={[
|
style={[
|
||||||
a.justify_center,
|
a.justify_center,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
a.border,
|
|
||||||
a.rounded_full,
|
a.rounded_full,
|
||||||
t.atoms.border_contrast_high,
|
t.atoms.border_contrast_high,
|
||||||
{
|
{
|
||||||
|
borderWidth: 1,
|
||||||
height: 20,
|
height: 20,
|
||||||
width: 20,
|
width: 20,
|
||||||
},
|
},
|
||||||
@@ -456,3 +460,5 @@ export function Radio() {
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Platform = isNative ? Switch : Checkbox
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function useFollowMethods({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.error(`useFollowMethods: failed to follow`, {message: String(e)})
|
logger.error(`useFollowMethods: failed to follow`, {message: String(e)})
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -47,7 +47,7 @@ export function useFollowMethods({
|
|||||||
message: String(e),
|
message: String(e),
|
||||||
})
|
})
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import {createSinglePathSVG} from './TEMPLATE'
|
|
||||||
|
|
||||||
export const SettingsGear2_Stroke2_Corner0_Rounded = createSinglePathSVG({
|
|
||||||
path: 'M11.1 2a1 1 0 0 0-.832.445L8.851 4.57 6.6 4.05a1 1 0 0 0-.932.268l-1.35 1.35a1 1 0 0 0-.267.932l.52 2.251-2.126 1.417A1 1 0 0 0 2 11.1v1.8a1 1 0 0 0 .445.832l2.125 1.417-.52 2.251a1 1 0 0 0 .268.932l1.35 1.35a1 1 0 0 0 .932.267l2.251-.52 1.417 2.126A1 1 0 0 0 11.1 22h1.8a1 1 0 0 0 .832-.445l1.417-2.125 2.251.52a1 1 0 0 0 .932-.268l1.35-1.35a1 1 0 0 0 .267-.932l-.52-2.251 2.126-1.417A1 1 0 0 0 22 12.9v-1.8a1 1 0 0 0-.445-.832L19.43 8.851l.52-2.251a1 1 0 0 0-.268-.932l-1.35-1.35a1 1 0 0 0-.932-.267l-2.251.52-1.417-2.126A1 1 0 0 0 12.9 2h-1.8Zm-.968 4.255L11.635 4h.73l1.503 2.255a1 1 0 0 0 1.057.42l2.385-.551.566.566-.55 2.385a1 1 0 0 0 .42 1.057L20 11.635v.73l-2.255 1.503a1 1 0 0 0-.42 1.057l.551 2.385-.566.566-2.385-.55a1 1 0 0 0-1.057.42L12.365 20h-.73l-1.503-2.255a1 1 0 0 0-1.057-.42l-2.385.551-.566-.566.55-2.385a1 1 0 0 0-.42-1.057L4 12.365v-.73l2.255-1.503a1 1 0 0 0 .42-1.057L6.123 6.69l.566-.566 2.385.55a1 1 0 0 0 1.057-.42ZM8 12a4 4 0 1 1 8 0 4 4 0 0 1-8 0Zm4-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z',
|
|
||||||
})
|
|
||||||
@@ -241,7 +241,7 @@ function AppealForm({
|
|||||||
},
|
},
|
||||||
onError: err => {
|
onError: err => {
|
||||||
logger.error('Failed to submit label appeal', {message: err})
|
logger.error('Failed to submit label appeal', {message: err})
|
||||||
Toast.show(_(msg`Failed to submit appeal, please try again.`))
|
Toast.show(_(msg`Failed to submit appeal, please try again.`), 'xmark')
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
control.close()
|
control.close()
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import {timeout} from './timeout'
|
import {timeout} from './timeout'
|
||||||
|
|
||||||
export async function until(
|
export async function until<T>(
|
||||||
retries: number,
|
retries: number,
|
||||||
delay: number,
|
delay: number,
|
||||||
cond: (v: any, err: any) => boolean,
|
cond: (v: T, err: any) => boolean,
|
||||||
fn: () => Promise<any>,
|
fn: () => Promise<T>,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
while (retries > 0) {
|
while (retries > 0) {
|
||||||
try {
|
try {
|
||||||
@@ -13,7 +13,9 @@ export async function until(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (cond(undefined, e)) {
|
// TODO: change the type signature of cond to accept undefined
|
||||||
|
// however this breaks every existing usage of until -sfn
|
||||||
|
if (cond(undefined as unknown as T, e)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,14 +17,14 @@ export async function openPicker(opts?: ImagePickerOptions) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (response.assets && response.assets.length > 4) {
|
if (response.assets && response.assets.length > 4) {
|
||||||
Toast.show('You may only select up to 4 images')
|
Toast.show('You may only select up to 4 images', 'exclamation-circle')
|
||||||
}
|
}
|
||||||
|
|
||||||
return (response.assets ?? [])
|
return (response.assets ?? [])
|
||||||
.slice(0, 4)
|
.slice(0, 4)
|
||||||
.filter(asset => {
|
.filter(asset => {
|
||||||
if (asset.mimeType?.startsWith('image/')) return true
|
if (asset.mimeType?.startsWith('image/')) return true
|
||||||
Toast.show('Only image files are supported')
|
Toast.show('Only image files are supported', 'exclamation-circle')
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
.map(image => ({
|
.map(image => ({
|
||||||
|
|||||||
@@ -42,14 +42,13 @@ export type CommonNavigatorParams = {
|
|||||||
Hashtag: {tag: string; author?: string}
|
Hashtag: {tag: string; author?: string}
|
||||||
MessagesConversation: {conversation: string; embed?: string}
|
MessagesConversation: {conversation: string; embed?: string}
|
||||||
MessagesSettings: undefined
|
MessagesSettings: undefined
|
||||||
|
NotificationsSettings: undefined
|
||||||
Feeds: undefined
|
Feeds: undefined
|
||||||
Start: {name: string; rkey: string}
|
Start: {name: string; rkey: string}
|
||||||
StarterPack: {name: string; rkey: string; new?: boolean}
|
StarterPack: {name: string; rkey: string; new?: boolean}
|
||||||
StarterPackShort: {code: string}
|
StarterPackShort: {code: string}
|
||||||
StarterPackWizard: undefined
|
StarterPackWizard: undefined
|
||||||
StarterPackEdit: {
|
StarterPackEdit: {rkey?: string}
|
||||||
rkey?: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type BottomTabNavigatorParams = CommonNavigatorParams & {
|
export type BottomTabNavigatorParams = CommonNavigatorParams & {
|
||||||
@@ -69,7 +68,7 @@ export type SearchTabNavigatorParams = CommonNavigatorParams & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
|
export type NotificationsTabNavigatorParams = CommonNavigatorParams & {
|
||||||
Notifications: undefined
|
Notifications: {show?: 'all'}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
|
export type MyProfileTabNavigatorParams = CommonNavigatorParams & {
|
||||||
@@ -84,7 +83,7 @@ export type FlatNavigatorParams = CommonNavigatorParams & {
|
|||||||
Home: undefined
|
Home: undefined
|
||||||
Search: {q?: string}
|
Search: {q?: string}
|
||||||
Feeds: undefined
|
Feeds: undefined
|
||||||
Notifications: undefined
|
Notifications: {show?: 'all'}
|
||||||
Hashtag: {tag: string; author?: string}
|
Hashtag: {tag: string; author?: string}
|
||||||
Messages: {pushToConversation?: string; animation?: 'push' | 'pop'}
|
Messages: {pushToConversation?: string; animation?: 'push' | 'pop'}
|
||||||
}
|
}
|
||||||
@@ -96,7 +95,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
|
|||||||
Search: {q?: string}
|
Search: {q?: string}
|
||||||
Feeds: undefined
|
Feeds: undefined
|
||||||
NotificationsTab: undefined
|
NotificationsTab: undefined
|
||||||
Notifications: undefined
|
Notifications: {show?: 'all'}
|
||||||
MyProfileTab: undefined
|
MyProfileTab: undefined
|
||||||
Hashtag: {tag: string; author?: string}
|
Hashtag: {tag: string; author?: string}
|
||||||
MessagesTab: undefined
|
MessagesTab: undefined
|
||||||
@@ -105,9 +104,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
|
|||||||
StarterPack: {name: string; rkey: string; new?: boolean}
|
StarterPack: {name: string; rkey: string; new?: boolean}
|
||||||
StarterPackShort: {code: string}
|
StarterPackShort: {code: string}
|
||||||
StarterPackWizard: undefined
|
StarterPackWizard: undefined
|
||||||
StarterPackEdit: {
|
StarterPackEdit: {rkey?: string}
|
||||||
rkey?: string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE
|
// NOTE
|
||||||
|
|||||||
+1
-1
@@ -20,6 +20,6 @@ export async function shareUrl(url: string) {
|
|||||||
// React Native Share is not supported by web. Web Share API
|
// React Native Share is not supported by web. Web Share API
|
||||||
// has increasing but not full support, so default to clipboard
|
// has increasing but not full support, so default to clipboard
|
||||||
setStringAsync(url)
|
setStringAsync(url)
|
||||||
Toast.show('Copied to clipboard')
|
Toast.show('Copied to clipboard', 'clipboard-check')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ export type Gate =
|
|||||||
| 'suggested_follows_interstitial'
|
| 'suggested_follows_interstitial'
|
||||||
| 'ungroup_follow_backs'
|
| 'ungroup_follow_backs'
|
||||||
| 'videos'
|
| 'videos'
|
||||||
|
| 'small_avi_thumb'
|
||||||
|
|||||||
+634
-548
File diff suppressed because it is too large
Load Diff
+923
-1235
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+635
-553
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+634
-548
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+628
-554
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+635
-545
File diff suppressed because it is too large
Load Diff
+635
-545
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
@@ -5,6 +5,7 @@ export const router = new Router({
|
|||||||
Search: '/search',
|
Search: '/search',
|
||||||
Feeds: '/feeds',
|
Feeds: '/feeds',
|
||||||
Notifications: '/notifications',
|
Notifications: '/notifications',
|
||||||
|
NotificationsSettings: '/notifications/settings',
|
||||||
Settings: '/settings',
|
Settings: '/settings',
|
||||||
LanguageSettings: '/settings/language',
|
LanguageSettings: '/settings/language',
|
||||||
Lists: '/lists',
|
Lists: '/lists',
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ function DialogInner() {
|
|||||||
},
|
},
|
||||||
onError: err => {
|
onError: err => {
|
||||||
logger.error('Failed to submit chat appeal', {message: err})
|
logger.error('Failed to submit chat appeal', {message: err})
|
||||||
Toast.show(_(msg`Failed to submit appeal, please try again.`))
|
Toast.show(_(msg`Failed to submit appeal, please try again.`), 'xmark')
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
control.close()
|
control.close()
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function MessageInput({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||||
Toast.show(_(msg`Message is too long`))
|
Toast.show(_(msg`Message is too long`), 'xmark')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clearDraft()
|
clearDraft()
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function MessageInput({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
if (new Graphemer().countGraphemes(message) > MAX_DM_GRAPHEME_LENGTH) {
|
||||||
Toast.show(_(msg`Message is too long`))
|
Toast.show(_(msg`Message is too long`), 'xmark')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
clearDraft()
|
clearDraft()
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ function Inner() {
|
|||||||
title={_(msg`Something went wrong`)}
|
title={_(msg`Something went wrong`)}
|
||||||
message={_(msg`We couldn't load this conversation`)}
|
message={_(msg`We couldn't load this conversation`)}
|
||||||
onRetry={() => convoState.error.retry()}
|
onRetry={() => convoState.error.retry()}
|
||||||
|
sideBorders={false}
|
||||||
/>
|
/>
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ function DesktopHeader({
|
|||||||
a.gap_lg,
|
a.gap_lg,
|
||||||
a.px_lg,
|
a.px_lg,
|
||||||
a.pr_md,
|
a.pr_md,
|
||||||
a.py_md,
|
a.py_sm,
|
||||||
a.border_b,
|
a.border_b,
|
||||||
t.atoms.border_contrast_low,
|
t.atoms.border_contrast_low,
|
||||||
]}>
|
]}>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {useProfileQuery} from '#/state/queries/profile'
|
|||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {Divider} from '#/components/Divider'
|
import {Divider} from '#/components/Divider'
|
||||||
import * as Toggle from '#/components/forms/Toggle'
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
@@ -32,7 +32,7 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
|
|
||||||
const {mutate: updateDeclaration} = useUpdateActorDeclaration({
|
const {mutate: updateDeclaration} = useUpdateActorDeclaration({
|
||||||
onError: () => {
|
onError: () => {
|
||||||
Toast.show(_(msg`Failed to update settings`))
|
Toast.show(_(msg`Failed to update settings`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView sideBorders style={a.h_full_vh}>
|
<ScrollView stickyHeaderIndices={[0]}>
|
||||||
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
<ViewHeader title={_(msg`Chat Settings`)} showOnDesktop showBorder />
|
||||||
<View style={[a.p_lg, a.gap_md]}>
|
<View style={[a.p_lg, a.gap_md]}>
|
||||||
<Text style={[a.text_lg, a.font_bold]}>
|
<Text style={[a.text_lg, a.font_bold]}>
|
||||||
@@ -107,7 +107,7 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
a.rounded_md,
|
a.rounded_md,
|
||||||
t.atoms.bg_contrast_25,
|
t.atoms.bg_contrast_25,
|
||||||
]}>
|
]}>
|
||||||
<Text style={[t.atoms.text_contrast_high]}>
|
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||||
<Trans>
|
<Trans>
|
||||||
You can continue ongoing conversations regardless of which setting
|
You can continue ongoing conversations regardless of which setting
|
||||||
you choose.
|
you choose.
|
||||||
@@ -149,6 +149,6 @@ export function MessagesSettingsScreen({}: Props) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ let ProfileHeaderLabeler = ({
|
|||||||
_(
|
_(
|
||||||
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
|
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ let ProfileHeaderStandard = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to follow', {message: String(e)})
|
logger.error('Failed to follow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -126,7 +126,7 @@ let ProfileHeaderStandard = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unfollow', {message: String(e)})
|
logger.error('Failed to unfollow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -140,7 +140,7 @@ let ProfileHeaderStandard = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unblock account', {message: e})
|
logger.error('Failed to unblock account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [_, queueUnblock, track])
|
}, [_, queueUnblock, track])
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ function Header({
|
|||||||
captureAction(ProgressGuideAction.Follow, dids.length)
|
captureAction(ProgressGuideAction.Follow, dids.length)
|
||||||
Toast.show(_(msg`All accounts have been followed!`))
|
Toast.show(_(msg`All accounts have been followed!`))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`An error occurred while trying to follow all`))
|
Toast.show(_(msg`An error occurred while trying to follow all`), 'xmark')
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
}
|
}
|
||||||
@@ -672,7 +672,7 @@ function InvalidStarterPack({rkey}: {rkey: string}) {
|
|||||||
onError: e => {
|
onError: e => {
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
logger.error('Failed to delete invalid starter pack', {safeMessage: e})
|
logger.error('Failed to delete invalid starter pack', {safeMessage: e})
|
||||||
Toast.show(_(msg`Failed to delete starter pack`))
|
Toast.show(_(msg`Failed to delete starter pack`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,10 @@ function reducer(state: State, action: Action): State {
|
|||||||
break
|
break
|
||||||
case 'AddProfile':
|
case 'AddProfile':
|
||||||
if (state.profiles.length >= 51) {
|
if (state.profiles.length >= 51) {
|
||||||
Toast.show(msg`You may only add up to 50 profiles`.message ?? '')
|
Toast.show(
|
||||||
|
msg`You may only add up to 50 profiles`.message ?? '',
|
||||||
|
'info',
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
updatedState = {...state, profiles: [...state.profiles, action.profile]}
|
updatedState = {...state, profiles: [...state.profiles, action.profile]}
|
||||||
}
|
}
|
||||||
@@ -89,7 +92,7 @@ function reducer(state: State, action: Action): State {
|
|||||||
break
|
break
|
||||||
case 'AddFeed':
|
case 'AddFeed':
|
||||||
if (state.feeds.length >= 50) {
|
if (state.feeds.length >= 50) {
|
||||||
Toast.show(msg`You may only add up to 50 feeds`.message ?? '')
|
Toast.show(msg`You may only add up to 50 feeds`.message ?? '', 'info')
|
||||||
} else {
|
} else {
|
||||||
updatedState = {...state, feeds: [...state.feeds, action.feed]}
|
updatedState = {...state, feeds: [...state.feeds, action.feed]}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ function WizardInner({
|
|||||||
onError: e => {
|
onError: e => {
|
||||||
logger.error('Failed to create starter pack', {safeMessage: e})
|
logger.error('Failed to create starter pack', {safeMessage: e})
|
||||||
dispatch({type: 'SetProcessing', processing: false})
|
dispatch({type: 'SetProcessing', processing: false})
|
||||||
Toast.show(_(msg`Failed to create starter pack`))
|
Toast.show(_(msg`Failed to create starter pack`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const {mutate: editStarterPack} = useEditStarterPackMutation({
|
const {mutate: editStarterPack} = useEditStarterPackMutation({
|
||||||
@@ -235,7 +235,7 @@ function WizardInner({
|
|||||||
onError: e => {
|
onError: e => {
|
||||||
logger.error('Failed to edit starter pack', {safeMessage: e})
|
logger.error('Failed to edit starter pack', {safeMessage: e})
|
||||||
dispatch({type: 'SetProcessing', processing: false})
|
dispatch({type: 'SetProcessing', processing: false})
|
||||||
Toast.show(_(msg`Failed to create starter pack`))
|
Toast.show(_(msg`Failed to create starter pack`), 'xmark')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,13 @@ export function useActorAutocompleteQuery(
|
|||||||
},
|
},
|
||||||
select: React.useCallback(
|
select: React.useCallback(
|
||||||
(data: AppBskyActorDefs.ProfileViewBasic[]) => {
|
(data: AppBskyActorDefs.ProfileViewBasic[]) => {
|
||||||
return computeSuggestions(data, moderationOpts || DEFAULT_MOD_OPTS)
|
return computeSuggestions({
|
||||||
|
q: prefix,
|
||||||
|
searched: data,
|
||||||
|
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[moderationOpts],
|
[prefix, moderationOpts],
|
||||||
),
|
),
|
||||||
placeholderData: maintainData ? keepPreviousData : undefined,
|
placeholderData: maintainData ? keepPreviousData : undefined,
|
||||||
})
|
})
|
||||||
@@ -81,19 +85,25 @@ export function useActorAutocompleteFn() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return computeSuggestions(
|
return computeSuggestions({
|
||||||
res?.data.actors,
|
q: query,
|
||||||
moderationOpts || DEFAULT_MOD_OPTS,
|
searched: res?.data.actors,
|
||||||
)
|
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[queryClient, moderationOpts, agent],
|
[queryClient, moderationOpts, agent],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeSuggestions(
|
function computeSuggestions({
|
||||||
searched: AppBskyActorDefs.ProfileViewBasic[] = [],
|
q,
|
||||||
moderationOpts: ModerationOpts,
|
searched = [],
|
||||||
) {
|
moderationOpts,
|
||||||
|
}: {
|
||||||
|
q?: string
|
||||||
|
searched?: AppBskyActorDefs.ProfileViewBasic[]
|
||||||
|
moderationOpts: ModerationOpts
|
||||||
|
}) {
|
||||||
let items: AppBskyActorDefs.ProfileViewBasic[] = []
|
let items: AppBskyActorDefs.ProfileViewBasic[] = []
|
||||||
for (const item of searched) {
|
for (const item of searched) {
|
||||||
if (!items.find(item2 => item2.handle === item.handle)) {
|
if (!items.find(item2 => item2.handle === item.handle)) {
|
||||||
@@ -102,6 +112,7 @@ function computeSuggestions(
|
|||||||
}
|
}
|
||||||
return items.filter(profile => {
|
return items.filter(profile => {
|
||||||
const modui = moderateProfile(profile, moderationOpts).ui('profileList')
|
const modui = moderateProfile(profile, moderationOpts).ui('profileList')
|
||||||
return !modui.filter || isJustAMute(modui)
|
const isExactMatch = q && profile.handle.toLowerCase() === q
|
||||||
|
return isExactMatch || !modui.filter || isJustAMute(modui)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,11 +46,14 @@ const PAGE_SIZE = 30
|
|||||||
type RQPageParam = string | undefined
|
type RQPageParam = string | undefined
|
||||||
|
|
||||||
const RQKEY_ROOT = 'notification-feed'
|
const RQKEY_ROOT = 'notification-feed'
|
||||||
export function RQKEY() {
|
export function RQKEY(priority?: false) {
|
||||||
return [RQKEY_ROOT]
|
return [RQKEY_ROOT, priority]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
export function useNotificationFeedQuery(opts?: {
|
||||||
|
enabled?: boolean
|
||||||
|
overridePriorityNotifications?: boolean
|
||||||
|
}) {
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const moderationOpts = useModerationOpts()
|
const moderationOpts = useModerationOpts()
|
||||||
@@ -59,6 +62,10 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||||||
const lastPageCountRef = useRef(0)
|
const lastPageCountRef = useRef(0)
|
||||||
const gate = useGate()
|
const gate = useGate()
|
||||||
|
|
||||||
|
// false: force showing all notifications
|
||||||
|
// undefined: let the server decide
|
||||||
|
const priority = opts?.overridePriorityNotifications ? false : undefined
|
||||||
|
|
||||||
const query = useInfiniteQuery<
|
const query = useInfiniteQuery<
|
||||||
FeedPage,
|
FeedPage,
|
||||||
Error,
|
Error,
|
||||||
@@ -67,7 +74,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||||||
RQPageParam
|
RQPageParam
|
||||||
>({
|
>({
|
||||||
staleTime: STALE.INFINITY,
|
staleTime: STALE.INFINITY,
|
||||||
queryKey: RQKEY(),
|
queryKey: RQKEY(priority),
|
||||||
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
async queryFn({pageParam}: {pageParam: RQPageParam}) {
|
||||||
let page
|
let page
|
||||||
if (!pageParam) {
|
if (!pageParam) {
|
||||||
@@ -75,17 +82,17 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
|
|||||||
page = unreads.getCachedUnreadPage()
|
page = unreads.getCachedUnreadPage()
|
||||||
}
|
}
|
||||||
if (!page) {
|
if (!page) {
|
||||||
page = (
|
const {page: fetchedPage} = await fetchPage({
|
||||||
await fetchPage({
|
agent,
|
||||||
agent,
|
limit: PAGE_SIZE,
|
||||||
limit: PAGE_SIZE,
|
cursor: pageParam,
|
||||||
cursor: pageParam,
|
queryClient,
|
||||||
queryClient,
|
moderationOpts,
|
||||||
moderationOpts,
|
fetchAdditionalData: true,
|
||||||
fetchAdditionalData: true,
|
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
||||||
shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'),
|
priority,
|
||||||
})
|
})
|
||||||
).page
|
page = fetchedPage
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the first page has an unread, mark all read
|
// if the first page has an unread, mark all read
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {msg} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {until} from '#/lib/async/until'
|
||||||
|
import {logger} from '#/logger'
|
||||||
|
import {RQKEY as RQKEY_NOTIFS} from '#/state/queries/notifications/feed'
|
||||||
|
import {invalidateCachedUnreadPage} from '#/state/queries/notifications/unread'
|
||||||
|
import {useAgent} from '#/state/session'
|
||||||
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
|
||||||
|
export function useNotificationsSettingsMutation() {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const agent = useAgent()
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (keys: string[]) => {
|
||||||
|
const enabled = keys[0] === 'enabled'
|
||||||
|
|
||||||
|
await agent.api.app.bsky.notification.putPreferences({
|
||||||
|
priority: enabled,
|
||||||
|
})
|
||||||
|
|
||||||
|
await until(
|
||||||
|
5, // 5 tries
|
||||||
|
1e3, // 1s delay between tries
|
||||||
|
res => res.data.priority === enabled,
|
||||||
|
() => agent.api.app.bsky.notification.listNotifications({limit: 1}),
|
||||||
|
)
|
||||||
|
|
||||||
|
eagerlySetCachedPriority(queryClient, enabled)
|
||||||
|
},
|
||||||
|
onError: err => {
|
||||||
|
logger.error('Failed to save notification preferences', {
|
||||||
|
safeMessage: err,
|
||||||
|
})
|
||||||
|
Toast.show(
|
||||||
|
_(msg`Failed to save notification preferences, please try again`),
|
||||||
|
'xmark',
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
Toast.show(_(msg`Preference saved`))
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
|
invalidateCachedUnreadPage()
|
||||||
|
queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS()})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function eagerlySetCachedPriority(
|
||||||
|
queryClient: ReturnType<typeof useQueryClient>,
|
||||||
|
enabled: boolean,
|
||||||
|
) {
|
||||||
|
queryClient.setQueryData(RQKEY_NOTIFS(), (old: any) => {
|
||||||
|
if (!old) return old
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
pages: old.pages.map((page: any) => {
|
||||||
|
return {
|
||||||
|
...page,
|
||||||
|
priority: enabled,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ export interface FeedPage {
|
|||||||
cursor: string | undefined
|
cursor: string | undefined
|
||||||
seenAt: Date
|
seenAt: Date
|
||||||
items: FeedNotification[]
|
items: FeedNotification[]
|
||||||
|
priority: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CachedFeedPage {
|
export interface CachedFeedPage {
|
||||||
|
|||||||
@@ -39,10 +39,15 @@ export async function fetchPage({
|
|||||||
moderationOpts: ModerationOpts | undefined
|
moderationOpts: ModerationOpts | undefined
|
||||||
fetchAdditionalData: boolean
|
fetchAdditionalData: boolean
|
||||||
shouldUngroupFollowBacks?: () => boolean
|
shouldUngroupFollowBacks?: () => boolean
|
||||||
}): Promise<{page: FeedPage; indexedAt: string | undefined}> {
|
priority?: boolean
|
||||||
|
}): Promise<{
|
||||||
|
page: FeedPage
|
||||||
|
indexedAt: string | undefined
|
||||||
|
}> {
|
||||||
const res = await agent.listNotifications({
|
const res = await agent.listNotifications({
|
||||||
limit,
|
limit,
|
||||||
cursor,
|
cursor,
|
||||||
|
// priority,
|
||||||
})
|
})
|
||||||
|
|
||||||
const indexedAt = res.data.notifications[0]?.indexedAt
|
const indexedAt = res.data.notifications[0]?.indexedAt
|
||||||
@@ -88,6 +93,7 @@ export async function fetchPage({
|
|||||||
cursor: res.data.cursor,
|
cursor: res.data.cursor,
|
||||||
seenAt,
|
seenAt,
|
||||||
items: notifsGrouped,
|
items: notifsGrouped,
|
||||||
|
priority: res.data.priority ?? false,
|
||||||
},
|
},
|
||||||
indexedAt,
|
indexedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function useVideoState({setError}: {setError: (error: string) => void}) {
|
|||||||
onError: (e: any) => {
|
onError: (e: any) => {
|
||||||
// Don't log these errors in sentry, just let the user know
|
// Don't log these errors in sentry, just let the user know
|
||||||
if (e instanceof VideoTooLargeError) {
|
if (e instanceof VideoTooLargeError) {
|
||||||
Toast.show(_(msg`Videos cannot be larger than 100MB`))
|
Toast.show(_(msg`Videos cannot be larger than 100MB`), 'xmark')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.error('Failed to compress video', {safeError: e})
|
logger.error('Failed to compress video', {safeError: e})
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function FeedSourceCardLoaded({
|
|||||||
])
|
])
|
||||||
Toast.show(_(msg`Added to my feeds`))
|
Toast.show(_(msg`Added to my feeds`))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting your server`))
|
Toast.show(_(msg`There was an issue contacting your server`), 'xmark')
|
||||||
logger.error('Failed to save feed', {message: e})
|
logger.error('Failed to save feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [_, feed, pinOnSave, addSavedFeeds, isSaved])
|
}, [_, feed, pinOnSave, addSavedFeeds, isSaved])
|
||||||
@@ -138,7 +138,7 @@ export function FeedSourceCardLoaded({
|
|||||||
// await item.unsave()
|
// await item.unsave()
|
||||||
Toast.show(_(msg`Removed from my feeds`))
|
Toast.show(_(msg`Removed from my feeds`))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting your server`))
|
Toast.show(_(msg`There was an issue contacting your server`), 'xmark')
|
||||||
logger.error('Failed to unsave feed', {message: e})
|
logger.error('Failed to unsave feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [_, removeFeed, savedFeedConfig])
|
}, [_, removeFeed, savedFeedConfig])
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
|
|||||||
const saveImageToAlbumWithToasts = React.useCallback(
|
const saveImageToAlbumWithToasts = React.useCallback(
|
||||||
async (uri: string) => {
|
async (uri: string) => {
|
||||||
if (!permissionResponse || permissionResponse.granted === false) {
|
if (!permissionResponse || permissionResponse.granted === false) {
|
||||||
Toast.show(_(msg`Permission to access camera roll is required.`))
|
Toast.show(
|
||||||
|
_(msg`Permission to access camera roll is required.`),
|
||||||
|
'info',
|
||||||
|
)
|
||||||
if (permissionResponse?.canAskAgain) {
|
if (permissionResponse?.canAskAgain) {
|
||||||
requestPermission()
|
requestPermission()
|
||||||
} else {
|
} else {
|
||||||
@@ -74,6 +77,7 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
|
|||||||
_(
|
_(
|
||||||
msg`Permission to access camera roll was denied. Please enable it in your system settings.`,
|
msg`Permission to access camera roll was denied. Please enable it in your system settings.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -83,7 +87,7 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
|
|||||||
await saveImageToMediaLibrary({uri})
|
await saveImageToMediaLibrary({uri})
|
||||||
Toast.show(_(msg`Saved to your camera roll`))
|
Toast.show(_(msg`Saved to your camera roll`))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
Toast.show(_(msg`Failed to save image: ${String(e)}`))
|
Toast.show(_(msg`Failed to save image: ${String(e)}`), 'xmark')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[permissionResponse, requestPermission, _],
|
[permissionResponse, requestPermission, _],
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function Component({}: {}) {
|
|||||||
const onCopy = React.useCallback(() => {
|
const onCopy = React.useCallback(() => {
|
||||||
if (appPassword) {
|
if (appPassword) {
|
||||||
setStringAsync(appPassword)
|
setStringAsync(appPassword)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
setWasCopied(true)
|
setWasCopied(true)
|
||||||
}
|
}
|
||||||
}, [appPassword, _])
|
}, [appPassword, _])
|
||||||
@@ -93,7 +93,7 @@ export function Component({}: {}) {
|
|||||||
_(
|
_(
|
||||||
msg`Please enter a name for your app password. All spaces is not allowed.`,
|
msg`Please enter a name for your app password. All spaces is not allowed.`,
|
||||||
),
|
),
|
||||||
'times',
|
'xmark',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -101,13 +101,13 @@ export function Component({}: {}) {
|
|||||||
if (name.length < 4) {
|
if (name.length < 4) {
|
||||||
Toast.show(
|
Toast.show(
|
||||||
_(msg`App Password names must be at least 4 characters long.`),
|
_(msg`App Password names must be at least 4 characters long.`),
|
||||||
'times',
|
'xmark',
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passwords?.find(p => p.name === name)) {
|
if (passwords?.find(p => p.name === name)) {
|
||||||
Toast.show(_(msg`This name is already in use`), 'times')
|
Toast.show(_(msg`This name is already in use`), 'xmark')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,11 +116,11 @@ export function Component({}: {}) {
|
|||||||
if (newPassword) {
|
if (newPassword) {
|
||||||
setAppPassword(newPassword.password)
|
setAppPassword(newPassword.password)
|
||||||
} else {
|
} else {
|
||||||
Toast.show(_(msg`Failed to create app password.`), 'times')
|
Toast.show(_(msg`Failed to create app password.`), 'xmark')
|
||||||
// TODO: better error handling (?)
|
// TODO: better error handling (?)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`Failed to create app password.`), 'times')
|
Toast.show(_(msg`Failed to create app password.`), 'xmark')
|
||||||
logger.error('Failed to create app password', {message: e})
|
logger.error('Failed to create app password', {message: e})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +137,7 @@ export function Component({}: {}) {
|
|||||||
_(
|
_(
|
||||||
msg`App Password names can only contain letters, numbers, spaces, dashes, and underscores.`,
|
msg`App Password names can only contain letters, numbers, spaces, dashes, and underscores.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ function CustomHandleForm({
|
|||||||
// =
|
// =
|
||||||
const onPressCopy = React.useCallback(() => {
|
const onPressCopy = React.useCallback(() => {
|
||||||
setStringAsync(isDNSForm ? `did=${currentAccount.did}` : currentAccount.did)
|
setStringAsync(isDNSForm ? `did=${currentAccount.did}` : currentAccount.did)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
}, [currentAccount, isDNSForm, _])
|
}, [currentAccount, isDNSForm, _])
|
||||||
const onChangeHandle = React.useCallback(
|
const onChangeHandle = React.useCallback(
|
||||||
(v: string) => {
|
(v: string) => {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ function InviteCode({
|
|||||||
|
|
||||||
const onPress = React.useCallback(() => {
|
const onPress = React.useCallback(() => {
|
||||||
setStringAsync(invite.code)
|
setStringAsync(invite.code)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
setInviteCopied(invite.code)
|
setInviteCopied(invite.code)
|
||||||
}, [setInviteCopied, invite, _])
|
}, [setInviteCopied, invite, _])
|
||||||
|
|
||||||
|
|||||||
@@ -7,32 +7,33 @@ import {
|
|||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
|
||||||
import {ScrollView, TextInput} from './util'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Text} from '../util/text/Text'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {Button} from '../util/forms/Button'
|
|
||||||
import {UserAvatar} from '../util/UserAvatar'
|
|
||||||
import * as Toast from '../util/Toast'
|
|
||||||
import {s, colors} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {isWeb} from 'platform/detection'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
|
||||||
import {cleanError} from 'lib/strings/errors'
|
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
|
||||||
import {HITSLOP_20} from '#/lib/constants'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {HITSLOP_20} from '#/lib/constants'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
|
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
||||||
import {
|
import {
|
||||||
useDangerousListMembershipsQuery,
|
|
||||||
getMembership,
|
getMembership,
|
||||||
ListMembersip,
|
ListMembersip,
|
||||||
|
useDangerousListMembershipsQuery,
|
||||||
useListMembershipAddMutation,
|
useListMembershipAddMutation,
|
||||||
useListMembershipRemoveMutation,
|
useListMembershipRemoveMutation,
|
||||||
} from '#/state/queries/list-memberships'
|
} from '#/state/queries/list-memberships'
|
||||||
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
|
import {useIsKeyboardVisible} from 'lib/hooks/useIsKeyboardVisible'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
||||||
|
import {cleanError} from 'lib/strings/errors'
|
||||||
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
|
import {colors, s} from 'lib/styles'
|
||||||
|
import {isWeb} from 'platform/detection'
|
||||||
|
import {Button} from '../util/forms/Button'
|
||||||
|
import {Text} from '../util/text/Text'
|
||||||
|
import * as Toast from '../util/Toast'
|
||||||
|
import {UserAvatar} from '../util/UserAvatar'
|
||||||
|
import {ScrollView, TextInput} from './util'
|
||||||
|
|
||||||
export const snapPoints = ['90%']
|
export const snapPoints = ['90%']
|
||||||
|
|
||||||
@@ -200,7 +201,7 @@ function UserResult({
|
|||||||
onChange?.('remove', profile)
|
onChange?.('remove', profile)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(cleanError(e))
|
Toast.show(cleanError(e), 'xmark')
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ function ListItem({
|
|||||||
onRemove?.(list.uri)
|
onRemove?.(list.uri)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(cleanError(e))
|
Toast.show(cleanError(e), 'xmark')
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,13 @@ export function Feed({
|
|||||||
onPressTryAgain,
|
onPressTryAgain,
|
||||||
onScrolledDownChange,
|
onScrolledDownChange,
|
||||||
ListHeaderComponent,
|
ListHeaderComponent,
|
||||||
|
overridePriorityNotifications,
|
||||||
}: {
|
}: {
|
||||||
scrollElRef?: ListRef
|
scrollElRef?: ListRef
|
||||||
onPressTryAgain?: () => void
|
onPressTryAgain?: () => void
|
||||||
onScrolledDownChange: (isScrolledDown: boolean) => void
|
onScrolledDownChange: (isScrolledDown: boolean) => void
|
||||||
ListHeaderComponent?: () => JSX.Element
|
ListHeaderComponent?: () => JSX.Element
|
||||||
|
overridePriorityNotifications?: boolean
|
||||||
}) {
|
}) {
|
||||||
const initialNumToRender = useInitialNumToRender()
|
const initialNumToRender = useInitialNumToRender()
|
||||||
|
|
||||||
@@ -59,7 +61,10 @@ export function Feed({
|
|||||||
hasNextPage,
|
hasNextPage,
|
||||||
isFetchingNextPage,
|
isFetchingNextPage,
|
||||||
fetchNextPage,
|
fetchNextPage,
|
||||||
} = useNotificationFeedQuery({enabled: !!moderationOpts})
|
} = useNotificationFeedQuery({
|
||||||
|
enabled: !!moderationOpts,
|
||||||
|
overridePriorityNotifications,
|
||||||
|
})
|
||||||
const isEmpty = !isFetching && !data?.pages[0]?.items.length
|
const isEmpty = !isFetching && !data?.pages[0]?.items.length
|
||||||
|
|
||||||
const items = React.useMemo(() => {
|
const items = React.useMemo(() => {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ function PostThreadFollowBtnLoaded({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to follow', {message: String(e)})
|
logger.error('Failed to follow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -108,7 +108,7 @@ function PostThreadFollowBtnLoaded({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unfollow', {message: String(e)})
|
logger.error('Failed to unfollow', {message: String(e)})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -144,6 +144,7 @@ function FeedgenErrorMessage({
|
|||||||
_l(
|
_l(
|
||||||
msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
|
msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'exclamation-circle',
|
||||||
)
|
)
|
||||||
logger.error('Failed to remove feed', {message: err})
|
logger.error('Failed to remove feed', {message: err})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
|
|||||||
_(
|
_(
|
||||||
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'exclamation-circle',
|
||||||
)
|
)
|
||||||
logger.error('Failed up update feeds', {message: err})
|
logger.error('Failed up update feeds', {message: err})
|
||||||
}
|
}
|
||||||
@@ -69,6 +70,7 @@ export function FeedShutdownMsg({feedUri}: {feedUri: string}) {
|
|||||||
_(
|
_(
|
||||||
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'exclamation-circle',
|
||||||
)
|
)
|
||||||
logger.error('Failed up update feeds', {message: err})
|
logger.error('Failed up update feeds', {message: err})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ let FeedSlice = ({
|
|||||||
record={slice.items[last].record}
|
record={slice.items[last].record}
|
||||||
reason={slice.items[last].reason}
|
reason={slice.items[last].reason}
|
||||||
feedContext={slice.items[last].feedContext}
|
feedContext={slice.items[last].feedContext}
|
||||||
parentAuthor={slice.items[2].parentAuthor}
|
parentAuthor={slice.items[last].parentAuthor}
|
||||||
showReplyTo={false}
|
showReplyTo={false}
|
||||||
moderation={slice.items[last].moderation}
|
moderation={slice.items[last].moderation}
|
||||||
isThreadParent={isThreadParentAt(slice.items, last)}
|
isThreadParent={isThreadParentAt(slice.items, last)}
|
||||||
isThreadChild={isThreadChildAt(slice.items, last)}
|
isThreadChild={isThreadChildAt(slice.items, last)}
|
||||||
isParentBlocked={slice.items[2].isParentBlocked}
|
isParentBlocked={slice.items[last].isParentBlocked}
|
||||||
isThreadLastChild
|
isThreadLastChild
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function FollowButton({
|
|||||||
await queueFollow()
|
await queueFollow()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ export function FollowButton({
|
|||||||
await queueUnfollow()
|
await queueUnfollow()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ function SuggestedFollow({
|
|||||||
await queueFollow()
|
await queueFollow()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [queueFollow, track, _])
|
}, [queueFollow, track, _])
|
||||||
@@ -195,7 +195,7 @@ function SuggestedFollow({
|
|||||||
await queueUnfollow()
|
await queueUnfollow()
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
Toast.show(_(msg`An issue occurred, please try again.`))
|
Toast.show(_(msg`An issue occurred, please try again.`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [queueUnfollow, _])
|
}, [queueUnfollow, _])
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unmute account', {message: e})
|
logger.error('Failed to unmute account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -119,7 +119,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to mute account', {message: e})
|
logger.error('Failed to mute account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +134,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unblock account', {message: e})
|
logger.error('Failed to unblock account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -145,7 +145,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to block account', {message: e})
|
logger.error('Failed to block account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -159,7 +159,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to follow account', {message: e})
|
logger.error('Failed to follow account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [_, queueFollow, track])
|
}, [_, queueFollow, track])
|
||||||
@@ -172,7 +172,7 @@ let ProfileMenu = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to unfollow account', {message: e})
|
logger.error('Failed to unfollow account', {message: e})
|
||||||
Toast.show(_(msg`There was an issue! ${e.toString()}`))
|
Toast.show(_(msg`There was an issue! ${e.toString()}`), 'xmark')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [_, queueUnfollow, track])
|
}, [_, queueUnfollow, track])
|
||||||
|
|||||||
+75
-69
@@ -1,87 +1,93 @@
|
|||||||
|
import React, {useEffect, useState} from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import Animated, {FadeInUp, FadeOutUp} from 'react-native-reanimated'
|
||||||
import RootSiblings from 'react-native-root-siblings'
|
import RootSiblings from 'react-native-root-siblings'
|
||||||
import React from 'react'
|
import {useSafeAreaInsets} from 'react-native-safe-area-context'
|
||||||
import {Animated, StyleSheet, View} from 'react-native'
|
import {
|
||||||
import {Props as FontAwesomeProps} from '@fortawesome/react-native-fontawesome'
|
FontAwesomeIcon,
|
||||||
import {Text} from './text/Text'
|
Props as FontAwesomeProps,
|
||||||
import {colors} from 'lib/styles'
|
} from '@fortawesome/react-native-fontawesome'
|
||||||
import {useTheme} from 'lib/ThemeContext'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
|
import {Text} from '#/components/Typography'
|
||||||
import {IS_TEST} from '#/env'
|
import {IS_TEST} from '#/env'
|
||||||
|
|
||||||
const TIMEOUT = 4e3
|
const TIMEOUT = 2e3
|
||||||
|
|
||||||
export function show(
|
export function show(
|
||||||
message: string,
|
message: string,
|
||||||
_icon: FontAwesomeProps['icon'] = 'check',
|
icon: FontAwesomeProps['icon'] = 'check',
|
||||||
) {
|
) {
|
||||||
if (IS_TEST) return
|
if (IS_TEST) return
|
||||||
const item = new RootSiblings(<Toast message={message} />)
|
const item = new RootSiblings(<Toast message={message} icon={icon} />)
|
||||||
|
// timeout has some leeway to account for the animation
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
item.destroy()
|
item.destroy()
|
||||||
}, TIMEOUT)
|
}, TIMEOUT + 1e3)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Toast({message}: {message: string}) {
|
function Toast({
|
||||||
const theme = useTheme()
|
message,
|
||||||
const pal = usePalette('default')
|
icon,
|
||||||
const interp = useAnimatedValue(0)
|
}: {
|
||||||
|
message: string
|
||||||
|
icon: FontAwesomeProps['icon']
|
||||||
|
}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {top} = useSafeAreaInsets()
|
||||||
|
|
||||||
React.useEffect(() => {
|
// for the exit animation to work on iOS the animated component
|
||||||
Animated.sequence([
|
// must not be the root component
|
||||||
Animated.timing(interp, {
|
// so we need to wrap it in a view and unmount the toast ahead of time
|
||||||
toValue: 1,
|
const [alive, setAlive] = useState(true)
|
||||||
duration: 150,
|
|
||||||
useNativeDriver: true,
|
useEffect(() => {
|
||||||
}),
|
setTimeout(() => {
|
||||||
Animated.delay(3700),
|
setAlive(false)
|
||||||
Animated.timing(interp, {
|
}, TIMEOUT)
|
||||||
toValue: 0,
|
}, [])
|
||||||
duration: 150,
|
|
||||||
useNativeDriver: true,
|
|
||||||
}),
|
|
||||||
]).start()
|
|
||||||
})
|
|
||||||
|
|
||||||
const opacityStyle = {opacity: interp}
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container} pointerEvents="none">
|
<View
|
||||||
<Animated.View
|
style={[a.absolute, {top: top + 15, left: 16, right: 16}]}
|
||||||
style={[
|
pointerEvents="none">
|
||||||
pal.view,
|
{alive && (
|
||||||
pal.border,
|
<Animated.View
|
||||||
styles.toast,
|
entering={FadeInUp}
|
||||||
theme.colorScheme === 'dark' && styles.toastDark,
|
exiting={FadeOutUp}
|
||||||
opacityStyle,
|
style={[
|
||||||
]}>
|
a.flex_1,
|
||||||
<Text type="lg-medium" style={pal.text}>
|
t.atoms.bg,
|
||||||
{message}
|
a.shadow_lg,
|
||||||
</Text>
|
a.rounded_sm,
|
||||||
</Animated.View>
|
t.atoms.border_contrast_medium,
|
||||||
|
a.rounded_sm,
|
||||||
|
a.px_md,
|
||||||
|
a.py_lg,
|
||||||
|
a.border,
|
||||||
|
a.flex_row,
|
||||||
|
a.gap_md,
|
||||||
|
]}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_shrink_0,
|
||||||
|
a.rounded_full,
|
||||||
|
{width: 32, height: 32},
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
]}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={icon}
|
||||||
|
size={16}
|
||||||
|
style={t.atoms.text_contrast_low}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[a.h_full, a.justify_center, a.flex_1]}>
|
||||||
|
<Text style={a.text_md}>{message}</Text>
|
||||||
|
</View>
|
||||||
|
</Animated.View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
position: 'absolute',
|
|
||||||
top: 60,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
toast: {
|
|
||||||
paddingHorizontal: 18,
|
|
||||||
paddingVertical: 10,
|
|
||||||
borderRadius: 24,
|
|
||||||
borderWidth: 1,
|
|
||||||
shadowColor: '#000',
|
|
||||||
shadowOpacity: 0.1,
|
|
||||||
shadowOffset: {width: 0, height: 4},
|
|
||||||
marginHorizontal: 6,
|
|
||||||
},
|
|
||||||
toastDark: {
|
|
||||||
backgroundColor: colors.gray6,
|
|
||||||
shadowOpacity: 0.5,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Note: the dataSet properties are used to leverage custom CSS in public/index.html
|
* Note: the dataSet properties are used to leverage custom CSS in public/index.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {useState, useEffect} from 'react'
|
import React, {useEffect, useState} from 'react'
|
||||||
import {StyleSheet, Text, View} from 'react-native'
|
import {StyleSheet, Text, View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
FontAwesomeIcon,
|
FontAwesomeIcon,
|
||||||
@@ -39,7 +39,7 @@ export const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
|
|||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={activeToast.icon}
|
icon={activeToast.icon}
|
||||||
size={24}
|
size={20}
|
||||||
style={styles.icon as FontAwesomeIconStyle}
|
style={styles.icon as FontAwesomeIconStyle}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.text}>{activeToast.text}</Text>
|
<Text style={styles.text}>{activeToast.text}</Text>
|
||||||
@@ -79,6 +79,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
|
flexShrink: 0,
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useGate} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {
|
import {
|
||||||
@@ -179,6 +180,7 @@ let UserAvatar = ({
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const backgroundColor = pal.colors.backgroundLight
|
const backgroundColor = pal.colors.backgroundLight
|
||||||
const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
|
const finalShape = overrideShape ?? (type === 'user' ? 'circle' : 'square')
|
||||||
|
const gate = useGate()
|
||||||
|
|
||||||
const aviStyle = useMemo(() => {
|
const aviStyle = useMemo(() => {
|
||||||
if (finalShape === 'square') {
|
if (finalShape === 'square') {
|
||||||
@@ -221,7 +223,12 @@ let UserAvatar = ({
|
|||||||
testID="userAvatarImage"
|
testID="userAvatarImage"
|
||||||
style={aviStyle}
|
style={aviStyle}
|
||||||
resizeMode="cover"
|
resizeMode="cover"
|
||||||
source={{uri: avatar}}
|
source={{
|
||||||
|
uri: hackModifyThumbnailPath(
|
||||||
|
avatar,
|
||||||
|
size < 90 && gate('small_avi_thumb'),
|
||||||
|
),
|
||||||
|
}}
|
||||||
blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
|
blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@@ -229,7 +236,12 @@ let UserAvatar = ({
|
|||||||
testID="userAvatarImage"
|
testID="userAvatarImage"
|
||||||
style={aviStyle}
|
style={aviStyle}
|
||||||
contentFit="cover"
|
contentFit="cover"
|
||||||
source={{uri: avatar}}
|
source={{
|
||||||
|
uri: hackModifyThumbnailPath(
|
||||||
|
avatar,
|
||||||
|
size < 90 && gate('small_avi_thumb'),
|
||||||
|
),
|
||||||
|
}}
|
||||||
blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
|
blurRadius={moderation?.blur ? BLUR_AMOUNT : 0}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -433,6 +445,16 @@ let PreviewableUserAvatar = ({
|
|||||||
PreviewableUserAvatar = memo(PreviewableUserAvatar)
|
PreviewableUserAvatar = memo(PreviewableUserAvatar)
|
||||||
export {PreviewableUserAvatar}
|
export {PreviewableUserAvatar}
|
||||||
|
|
||||||
|
// HACK
|
||||||
|
// We have started serving smaller avis but haven't updated lexicons to give the data properly
|
||||||
|
// manually string-replace to use the smaller ones
|
||||||
|
// -prf
|
||||||
|
function hackModifyThumbnailPath(uri: string, isEnabled: boolean): string {
|
||||||
|
return isEnabled
|
||||||
|
? uri.replace('/img/avatar/plain/', '/img/avatar_thumbnail/plain/')
|
||||||
|
: uri
|
||||||
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
editButtonContainer: {
|
editButtonContainer: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ let PostDropdownBtn = ({
|
|||||||
},
|
},
|
||||||
e => {
|
e => {
|
||||||
logger.error('Failed to delete post', {message: e})
|
logger.error('Failed to delete post', {message: e})
|
||||||
Toast.show(_(msg`Failed to delete post, please try again`))
|
Toast.show(_(msg`Failed to delete post, please try again`), 'xmark')
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}, [
|
}, [
|
||||||
@@ -177,7 +177,10 @@ let PostDropdownBtn = ({
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e?.name !== 'AbortError') {
|
if (e?.name !== 'AbortError') {
|
||||||
logger.error('Failed to toggle thread mute', {message: e})
|
logger.error('Failed to toggle thread mute', {message: e})
|
||||||
Toast.show(_(msg`Failed to toggle thread mute, please try again`))
|
Toast.show(
|
||||||
|
_(msg`Failed to toggle thread mute, please try again`),
|
||||||
|
'xmark',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isThreadMuted, unmuteThread, _, muteThread])
|
}, [isThreadMuted, unmuteThread, _, muteThread])
|
||||||
@@ -186,7 +189,7 @@ let PostDropdownBtn = ({
|
|||||||
const str = richTextToString(richText, true)
|
const str = richTextToString(richText, true)
|
||||||
|
|
||||||
Clipboard.setStringAsync(str)
|
Clipboard.setStringAsync(str)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
}, [_, richText])
|
}, [_, richText])
|
||||||
|
|
||||||
const onPressTranslate = React.useCallback(() => {
|
const onPressTranslate = React.useCallback(() => {
|
||||||
|
|||||||
@@ -354,7 +354,7 @@ let PostCtrls = ({
|
|||||||
onPress={e => {
|
onPress={e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
Clipboard.setStringAsync(feedContext)
|
Clipboard.setStringAsync(feedContext)
|
||||||
Toast.show(_(msg`Copied to clipboard`))
|
Toast.show(_(msg`Copied to clipboard`), 'clipboard-check')
|
||||||
}}>
|
}}>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight'
|
|||||||
import {faCircleCheck} from '@fortawesome/free-solid-svg-icons/faCircleCheck'
|
import {faCircleCheck} from '@fortawesome/free-solid-svg-icons/faCircleCheck'
|
||||||
import {faCircleDot} from '@fortawesome/free-solid-svg-icons/faCircleDot'
|
import {faCircleDot} from '@fortawesome/free-solid-svg-icons/faCircleDot'
|
||||||
import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation'
|
import {faCircleExclamation} from '@fortawesome/free-solid-svg-icons/faCircleExclamation'
|
||||||
|
import {faClipboardCheck} from '@fortawesome/free-solid-svg-icons/faClipboardCheck'
|
||||||
import {faClone} from '@fortawesome/free-solid-svg-icons/faClone'
|
import {faClone} from '@fortawesome/free-solid-svg-icons/faClone'
|
||||||
import {faCommentSlash} from '@fortawesome/free-solid-svg-icons/faCommentSlash'
|
import {faCommentSlash} from '@fortawesome/free-solid-svg-icons/faCommentSlash'
|
||||||
import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload'
|
import {faDownload} from '@fortawesome/free-solid-svg-icons/faDownload'
|
||||||
@@ -140,6 +141,7 @@ library.add(
|
|||||||
faCircleExclamation,
|
faCircleExclamation,
|
||||||
faCirclePlay,
|
faCirclePlay,
|
||||||
faCircleUser,
|
faCircleUser,
|
||||||
|
faClipboardCheck,
|
||||||
faClone,
|
faClone,
|
||||||
farClone,
|
farClone,
|
||||||
faComment,
|
faComment,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function AccessibilitySettingsScreen({}: Props) {
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
|
||||||
const requireAltTextEnabled = useRequireAltTextEnabled()
|
const requireAltTextEnabled = useRequireAltTextEnabled()
|
||||||
@@ -58,7 +58,7 @@ export function AccessibilitySettingsScreen({}: Props) {
|
|||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion} testID="accessibilitySettingsScreen">
|
<View style={s.hContentRegion} testID="accessibilitySettingsScreen">
|
||||||
<SimpleViewHeader
|
<SimpleViewHeader
|
||||||
showBackButton={isMobile}
|
showBackButton={isTabletOrMobile}
|
||||||
style={[
|
style={[
|
||||||
pal.border,
|
pal.border,
|
||||||
{borderBottomWidth: 1},
|
{borderBottomWidth: 1},
|
||||||
|
|||||||
@@ -1,11 +1,19 @@
|
|||||||
import React from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {ComposeIcon2} from '#/lib/icons'
|
||||||
|
import {
|
||||||
|
NativeStackScreenProps,
|
||||||
|
NotificationsTabNavigatorParams,
|
||||||
|
} from '#/lib/routes/types'
|
||||||
|
import {s} from '#/lib/styles'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {isNative} from '#/platform/detection'
|
import {isNative} from '#/platform/detection'
|
||||||
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
import {emitSoftReset, listenSoftReset} from '#/state/events'
|
||||||
@@ -17,37 +25,32 @@ import {
|
|||||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
import {truncateAndInvalidate} from '#/state/queries/util'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {Feed} from '#/view/com/notifications/Feed'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {FAB} from '#/view/com/util/fab/FAB'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
|
||||||
import {ComposeIcon2} from 'lib/icons'
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
import {
|
|
||||||
NativeStackScreenProps,
|
|
||||||
NotificationsTabNavigatorParams,
|
|
||||||
} from 'lib/routes/types'
|
|
||||||
import {colors, s} from 'lib/styles'
|
|
||||||
import {TextLink} from 'view/com/util/Link'
|
|
||||||
import {ListMethods} from 'view/com/util/List'
|
import {ListMethods} from 'view/com/util/List'
|
||||||
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Button} from '#/components/Button'
|
||||||
|
import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
|
||||||
|
import {Link} from '#/components/Link'
|
||||||
import {Loader} from '#/components/Loader'
|
import {Loader} from '#/components/Loader'
|
||||||
import {Feed} from '../com/notifications/Feed'
|
import {Text} from '#/components/Typography'
|
||||||
import {FAB} from '../com/util/fab/FAB'
|
|
||||||
import {MainScrollProvider} from '../com/util/MainScrollProvider'
|
|
||||||
import {ViewHeader} from '../com/util/ViewHeader'
|
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<
|
type Props = NativeStackScreenProps<
|
||||||
NotificationsTabNavigatorParams,
|
NotificationsTabNavigatorParams,
|
||||||
'Notifications'
|
'Notifications'
|
||||||
>
|
>
|
||||||
export function NotificationsScreen({}: Props) {
|
export function NotificationsScreen({route: {params}}: Props) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
|
||||||
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
|
||||||
const scrollElRef = React.useRef<ListMethods>(null)
|
const scrollElRef = React.useRef<ListMethods>(null)
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const pal = usePalette('default')
|
const t = useTheme()
|
||||||
const {isDesktop} = useWebMediaQueries()
|
const {isDesktop} = useWebMediaQueries()
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const unreadNotifs = useUnreadNotifications()
|
const unreadNotifs = useUnreadNotifications()
|
||||||
@@ -109,56 +112,87 @@ export function NotificationsScreen({}: Props) {
|
|||||||
return listenSoftReset(onPressLoadLatest)
|
return listenSoftReset(onPressLoadLatest)
|
||||||
}, [onPressLoadLatest, isScreenFocused])
|
}, [onPressLoadLatest, isScreenFocused])
|
||||||
|
|
||||||
|
const renderButton = useCallback(() => {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to="/notifications/settings"
|
||||||
|
label={_(msg`Notification settings`)}
|
||||||
|
size="small"
|
||||||
|
variant="ghost"
|
||||||
|
color="secondary"
|
||||||
|
shape="square"
|
||||||
|
style={[a.justify_center]}>
|
||||||
|
<SettingsIcon size="md" style={t.atoms.text_contrast_medium} />
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}, [_, t])
|
||||||
|
|
||||||
const ListHeaderComponent = React.useCallback(() => {
|
const ListHeaderComponent = React.useCallback(() => {
|
||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
pal.view,
|
t.atoms.bg,
|
||||||
{
|
a.flex_row,
|
||||||
flexDirection: 'row',
|
a.align_center,
|
||||||
alignItems: 'center',
|
a.justify_between,
|
||||||
justifyContent: 'space-between',
|
a.gap_lg,
|
||||||
paddingHorizontal: 18,
|
a.px_lg,
|
||||||
paddingVertical: 12,
|
a.pr_md,
|
||||||
},
|
a.py_sm,
|
||||||
]}>
|
]}>
|
||||||
<TextLink
|
<Button
|
||||||
type="title-lg"
|
label={_(msg`Notifications`)}
|
||||||
href="/notifications"
|
accessibilityHint={_(msg`Refresh notifications`)}
|
||||||
style={[pal.text, {fontWeight: 'bold'}]}
|
onPress={emitSoftReset}>
|
||||||
text={
|
{({hovered, pressed}) => (
|
||||||
<>
|
<Text
|
||||||
<Trans>Notifications</Trans>{' '}
|
style={[
|
||||||
|
a.text_2xl,
|
||||||
|
a.font_bold,
|
||||||
|
(hovered || pressed) && a.underline,
|
||||||
|
]}>
|
||||||
|
<Trans>Notifications</Trans>
|
||||||
{hasNew && (
|
{hasNew && (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
left: 4,
|
||||||
top: -8,
|
top: -8,
|
||||||
backgroundColor: colors.blue3,
|
backgroundColor: t.palette.primary_500,
|
||||||
width: 8,
|
width: 8,
|
||||||
height: 8,
|
height: 8,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</Text>
|
||||||
}
|
)}
|
||||||
onPress={emitSoftReset}
|
</Button>
|
||||||
/>
|
<View style={[a.flex_row, a.align_center, a.gap_sm]}>
|
||||||
{isLoadingLatest ? <Loader size="md" /> : <></>}
|
{isLoadingLatest ? <Loader size="md" /> : <></>}
|
||||||
|
{renderButton()}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return <></>
|
return <></>
|
||||||
}, [isDesktop, pal, hasNew, isLoadingLatest])
|
}, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest])
|
||||||
|
|
||||||
const renderHeaderSpinner = React.useCallback(() => {
|
const renderHeaderSpinner = React.useCallback(() => {
|
||||||
return (
|
return (
|
||||||
<View style={{width: 30, height: 20, alignItems: 'flex-end'}}>
|
<View
|
||||||
|
style={[
|
||||||
|
{width: 30, height: 20},
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_end,
|
||||||
|
a.gap_md,
|
||||||
|
]}>
|
||||||
{isLoadingLatest ? <Loader width={20} /> : <></>}
|
{isLoadingLatest ? <Loader width={20} /> : <></>}
|
||||||
|
{renderButton()}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}, [isLoadingLatest])
|
}, [renderButton, isLoadingLatest])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<CenteredView
|
||||||
@@ -176,6 +210,7 @@ export function NotificationsScreen({}: Props) {
|
|||||||
onScrolledDownChange={setIsScrolledDown}
|
onScrolledDownChange={setIsScrolledDown}
|
||||||
scrollElRef={scrollElRef}
|
scrollElRef={scrollElRef}
|
||||||
ListHeaderComponent={ListHeaderComponent}
|
ListHeaderComponent={ListHeaderComponent}
|
||||||
|
overridePriorityNotifications={params?.show === 'all'}
|
||||||
/>
|
/>
|
||||||
</MainScrollProvider>
|
</MainScrollProvider>
|
||||||
{(isScrolledDown || hasNew) && (
|
{(isScrolledDown || hasNew) && (
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import {msg, Trans} from '@lingui/macro'
|
||||||
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {AllNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
|
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
|
||||||
|
import {useNotificationsSettingsMutation} from '#/state/queries/notifications/settings'
|
||||||
|
import {ViewHeader} from '#/view/com/util/ViewHeader'
|
||||||
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {Error} from '#/components/Error'
|
||||||
|
import * as Toggle from '#/components/forms/Toggle'
|
||||||
|
import {Loader} from '#/components/Loader'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
type Props = NativeStackScreenProps<AllNavigatorParams, 'NotificationsSettings'>
|
||||||
|
export function NotificationsSettingsScreen({}: Props) {
|
||||||
|
const {_} = useLingui()
|
||||||
|
const t = useTheme()
|
||||||
|
|
||||||
|
const {data, isError: isQueryError, refetch} = useNotificationFeedQuery()
|
||||||
|
const serverPriority = data?.pages.at(0)?.priority
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutate: onChangePriority,
|
||||||
|
isPending: isMutationPending,
|
||||||
|
variables,
|
||||||
|
} = useNotificationsSettingsMutation()
|
||||||
|
|
||||||
|
const priority = isMutationPending
|
||||||
|
? variables[0] === 'enabled'
|
||||||
|
: serverPriority
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView stickyHeaderIndices={[0]}>
|
||||||
|
<ViewHeader
|
||||||
|
title={_(msg`Notification Settings`)}
|
||||||
|
showOnDesktop
|
||||||
|
showBorder
|
||||||
|
/>
|
||||||
|
{isQueryError ? (
|
||||||
|
<Error
|
||||||
|
title={_(msg`Oops!`)}
|
||||||
|
message={_(msg`Something went wrong!`)}
|
||||||
|
onRetry={refetch}
|
||||||
|
sideBorders={false}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View style={[a.p_lg, a.gap_md]}>
|
||||||
|
<Text style={[a.text_lg, a.font_bold]}>
|
||||||
|
<FontAwesomeIcon icon="flask" style={t.atoms.text} />{' '}
|
||||||
|
<Trans>Notification filters</Trans>
|
||||||
|
</Text>
|
||||||
|
<Toggle.Group
|
||||||
|
label={_(msg`Priority notifications`)}
|
||||||
|
type="checkbox"
|
||||||
|
values={priority ? ['enabled'] : []}
|
||||||
|
onChange={onChangePriority}
|
||||||
|
disabled={typeof priority !== 'boolean' || isMutationPending}>
|
||||||
|
<View>
|
||||||
|
<Toggle.Item
|
||||||
|
name="enabled"
|
||||||
|
label={_(msg`Enable priority notifications`)}
|
||||||
|
style={[a.justify_between, a.py_sm]}>
|
||||||
|
<Toggle.LabelText>
|
||||||
|
<Trans>Enable priority notifications</Trans>
|
||||||
|
</Toggle.LabelText>
|
||||||
|
{!data ? <Loader size="md" /> : <Toggle.Platform />}
|
||||||
|
</Toggle.Item>
|
||||||
|
</View>
|
||||||
|
</Toggle.Group>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.mt_sm,
|
||||||
|
a.px_xl,
|
||||||
|
a.py_lg,
|
||||||
|
a.rounded_md,
|
||||||
|
t.atoms.bg_contrast_25,
|
||||||
|
]}>
|
||||||
|
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
|
||||||
|
<Trans>
|
||||||
|
Experimental: When this preference is enabled, you'll only
|
||||||
|
receive reply and quote notifications from users you follow.
|
||||||
|
We'll continue to add more controls here over time.
|
||||||
|
</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ export function PreferencesExternalEmbeds({}: Props) {
|
|||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
const setMinimalShellMode = useSetMinimalShellMode()
|
||||||
const {screen} = useAnalytics()
|
const {screen} = useAnalytics()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
React.useCallback(() => {
|
React.useCallback(() => {
|
||||||
@@ -41,26 +41,23 @@ export function PreferencesExternalEmbeds({}: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={s.hContentRegion} testID="preferencesExternalEmbedsScreen">
|
<View style={s.hContentRegion} testID="preferencesExternalEmbedsScreen">
|
||||||
<SimpleViewHeader
|
|
||||||
showBackButton={isMobile}
|
|
||||||
style={[
|
|
||||||
pal.border,
|
|
||||||
{borderBottomWidth: 1},
|
|
||||||
!isMobile && {borderLeftWidth: 1, borderRightWidth: 1},
|
|
||||||
]}>
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
|
||||||
<Trans>External Media Preferences</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text style={pal.textLight}>
|
|
||||||
<Trans>Customize media from external sites.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</SimpleViewHeader>
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
// @ts-ignore web only -prf
|
// @ts-ignore web only -prf
|
||||||
dataSet={{'stable-gutters': 1}}
|
dataSet={{'stable-gutters': 1}}
|
||||||
contentContainerStyle={[pal.viewLight, {paddingBottom: 200}]}>
|
contentContainerStyle={[pal.viewLight, {paddingBottom: 75}]}>
|
||||||
|
<SimpleViewHeader
|
||||||
|
showBackButton={isTabletOrMobile}
|
||||||
|
style={[pal.border, {borderBottomWidth: 1}]}>
|
||||||
|
<View style={{flex: 1}}>
|
||||||
|
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
||||||
|
<Trans>External Media Preferences</Trans>
|
||||||
|
</Text>
|
||||||
|
<Text style={pal.textLight}>
|
||||||
|
<Trans>Customize media from external sites.</Trans>
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</SimpleViewHeader>
|
||||||
|
|
||||||
<View style={[pal.view]}>
|
<View style={[pal.view]}>
|
||||||
<View style={styles.infoCard}>
|
<View style={styles.infoCard}>
|
||||||
<Text style={pal.text}>
|
<Text style={pal.text}>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import React, {useState} from 'react'
|
import React, {useState} from 'react'
|
||||||
import {ScrollView, StyleSheet, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {msg, Plural, Trans} from '@lingui/macro'
|
import {msg, Plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
import {Slider} from '@miblanchard/react-native-slider'
|
import {Slider} from '@miblanchard/react-native-slider'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
|
import {colors, s} from '#/lib/styles'
|
||||||
|
import {isWeb} from '#/platform/detection'
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
useSetFeedViewPreferencesMutation,
|
useSetFeedViewPreferencesMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {ToggleButton} from '#/view/com/util/forms/ToggleButton'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
import {colors, s} from 'lib/styles'
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
import {isWeb} from 'platform/detection'
|
|
||||||
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
|
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
|
||||||
import {Text} from '../com/util/text/Text'
|
|
||||||
|
|
||||||
function RepliesThresholdInput({
|
function RepliesThresholdInput({
|
||||||
enabled,
|
enabled,
|
||||||
@@ -79,10 +79,10 @@ type Props = NativeStackScreenProps<
|
|||||||
CommonNavigatorParams,
|
CommonNavigatorParams,
|
||||||
'PreferencesFollowingFeed'
|
'PreferencesFollowingFeed'
|
||||||
>
|
>
|
||||||
export function PreferencesFollowingFeed({navigation}: Props) {
|
export function PreferencesFollowingFeed({}: Props) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {mutate: setFeedViewPref, variables} =
|
const {mutate: setFeedViewPref, variables} =
|
||||||
useSetFeedViewPreferencesMutation()
|
useSetFeedViewPreferencesMutation()
|
||||||
@@ -92,26 +92,25 @@ export function PreferencesFollowingFeed({navigation}: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<View testID="preferencesHomeFeedScreen" style={s.hContentRegion}>
|
||||||
testID="preferencesHomeFeedScreen"
|
<ScrollView
|
||||||
style={[
|
// @ts-ignore web only -sfn
|
||||||
pal.view,
|
dataSet={{'stable-gutters': 1}}
|
||||||
pal.border,
|
contentContainerStyle={{paddingBottom: 75}}>
|
||||||
styles.container,
|
<SimpleViewHeader
|
||||||
isTabletOrDesktop && styles.desktopContainer,
|
showBackButton={isTabletOrMobile}
|
||||||
]}>
|
style={[pal.border, {borderBottomWidth: 1}]}>
|
||||||
<ViewHeader title={_(msg`Following Feed Preferences`)} showOnDesktop />
|
<View style={{flex: 1}}>
|
||||||
<View
|
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
||||||
style={[
|
<Trans>Following Feed Preferences</Trans>
|
||||||
styles.titleSection,
|
</Text>
|
||||||
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
|
<Text style={pal.textLight}>
|
||||||
]}>
|
<Trans>
|
||||||
<Text type="xl" style={[pal.textLight, styles.description]}>
|
Fine-tune the content you see on your Following feed.
|
||||||
<Trans>Fine-tune the content you see on your Following feed.</Trans>
|
</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
</SimpleViewHeader>
|
||||||
<ScrollView>
|
|
||||||
<View style={styles.cardsContainer}>
|
<View style={styles.cardsContainer}>
|
||||||
<View style={[pal.viewLight, styles.card]}>
|
<View style={[pal.viewLight, styles.card]}>
|
||||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||||
@@ -253,7 +252,7 @@ export function PreferencesFollowingFeed({navigation}: Props) {
|
|||||||
|
|
||||||
<View style={[pal.viewLight, styles.card]}>
|
<View style={[pal.viewLight, styles.card]}>
|
||||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||||
<FontAwesomeIcon icon="flask" color={pal.colors.text} />
|
<FontAwesomeIcon icon="flask" color={pal.colors.text} />{' '}
|
||||||
<Trans>Show Posts from My Feeds</Trans>
|
<Trans>Show Posts from My Feeds</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={[pal.text, s.pb10]}>
|
<Text style={[pal.text, s.pb10]}>
|
||||||
@@ -288,42 +287,17 @@ export function PreferencesFollowingFeed({navigation}: Props) {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
</View>
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.btnContainer,
|
|
||||||
!isTabletOrDesktop && {borderTopWidth: 1, paddingHorizontal: 20},
|
|
||||||
pal.border,
|
|
||||||
]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="confirmBtn"
|
|
||||||
onPress={() => {
|
|
||||||
navigation.canGoBack()
|
|
||||||
? navigation.goBack()
|
|
||||||
: navigation.navigate('Settings')
|
|
||||||
}}
|
|
||||||
style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Confirm`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<Text style={[s.white, s.bold, s.f18]}>
|
|
||||||
<Trans>Done</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</CenteredView>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingBottom: 90,
|
|
||||||
},
|
},
|
||||||
desktopContainer: {
|
desktopContainer: {
|
||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
paddingBottom: 40,
|
|
||||||
},
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
paddingBottom: 30,
|
paddingBottom: 30,
|
||||||
@@ -338,6 +312,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
cardsContainer: {
|
cardsContainer: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 16,
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
import {ActivityIndicator, StyleSheet, View} from 'react-native'
|
||||||
ActivityIndicator,
|
|
||||||
ScrollView,
|
|
||||||
StyleSheet,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
} from 'react-native'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {Text} from '../com/util/text/Text'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {s, colors} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
|
|
||||||
import {RadioGroup} from 'view/com/util/forms/RadioGroup'
|
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {usePalette} from '#/lib/hooks/usePalette'
|
||||||
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
|
||||||
|
import {colors, s} from '#/lib/styles'
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
usePreferencesQuery,
|
||||||
useSetThreadViewPreferencesMutation,
|
useSetThreadViewPreferencesMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
|
import {RadioGroup} from '#/view/com/util/forms/RadioGroup'
|
||||||
|
import {ToggleButton} from '#/view/com/util/forms/ToggleButton'
|
||||||
|
import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
|
||||||
|
import {Text} from '#/view/com/util/text/Text'
|
||||||
|
import {ScrollView} from '#/view/com/util/Views'
|
||||||
|
import {atoms as a} from '#/alf'
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
|
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
|
||||||
export function PreferencesThreads({navigation}: Props) {
|
export function PreferencesThreads({}: Props) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {isTabletOrDesktop} = useWebMediaQueries()
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {mutate: setThreadViewPrefs, variables} =
|
const {mutate: setThreadViewPrefs, variables} =
|
||||||
useSetThreadViewPreferencesMutation()
|
useSetThreadViewPreferencesMutation()
|
||||||
@@ -42,27 +38,25 @@ export function PreferencesThreads({navigation}: Props) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView
|
<View testID="preferencesThreadsScreen" style={s.hContentRegion}>
|
||||||
testID="preferencesThreadsScreen"
|
<ScrollView
|
||||||
style={[
|
// @ts-ignore web only -prf
|
||||||
pal.view,
|
dataSet={{'stable-gutters': 1}}
|
||||||
pal.border,
|
contentContainerStyle={{paddingBottom: 75}}>
|
||||||
styles.container,
|
<SimpleViewHeader
|
||||||
isTabletOrDesktop && styles.desktopContainer,
|
showBackButton={isTabletOrMobile}
|
||||||
]}>
|
style={[pal.border, {borderBottomWidth: 1}]}>
|
||||||
<ViewHeader title={_(msg`Thread Preferences`)} showOnDesktop />
|
<View style={{flex: 1}}>
|
||||||
<View
|
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
|
||||||
style={[
|
<Trans>Thread Preferences</Trans>
|
||||||
styles.titleSection,
|
</Text>
|
||||||
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
|
<Text style={pal.textLight}>
|
||||||
]}>
|
<Trans>Fine-tune the discussion threads.</Trans>
|
||||||
<Text type="xl" style={[pal.textLight, styles.description]}>
|
</Text>
|
||||||
<Trans>Fine-tune the discussion threads.</Trans>
|
</View>
|
||||||
</Text>
|
</SimpleViewHeader>
|
||||||
</View>
|
|
||||||
|
|
||||||
{preferences ? (
|
{preferences ? (
|
||||||
<ScrollView>
|
|
||||||
<View style={styles.cardsContainer}>
|
<View style={styles.cardsContainer}>
|
||||||
<View style={[pal.viewLight, styles.card]}>
|
<View style={[pal.viewLight, styles.card]}>
|
||||||
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
<Text type="title-sm" style={[pal.text, s.pb5]}>
|
||||||
@@ -136,46 +130,21 @@ export function PreferencesThreads({navigation}: Props) {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
) : (
|
||||||
) : (
|
<ActivityIndicator style={a.flex_1} />
|
||||||
<ActivityIndicator />
|
)}
|
||||||
)}
|
</ScrollView>
|
||||||
|
</View>
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.btnContainer,
|
|
||||||
!isTabletOrDesktop && {borderTopWidth: 1, paddingHorizontal: 20},
|
|
||||||
pal.border,
|
|
||||||
]}>
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="confirmBtn"
|
|
||||||
onPress={() => {
|
|
||||||
navigation.canGoBack()
|
|
||||||
? navigation.goBack()
|
|
||||||
: navigation.navigate('Settings')
|
|
||||||
}}
|
|
||||||
style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Confirm`)}
|
|
||||||
accessibilityHint="">
|
|
||||||
<Text style={[s.white, s.bold, s.f18]}>
|
|
||||||
<Trans context="action">Done</Trans>
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</CenteredView>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingBottom: 90,
|
|
||||||
},
|
},
|
||||||
desktopContainer: {
|
desktopContainer: {
|
||||||
borderLeftWidth: 1,
|
borderLeftWidth: 1,
|
||||||
borderRightWidth: 1,
|
borderRightWidth: 1,
|
||||||
paddingBottom: 40,
|
|
||||||
},
|
},
|
||||||
titleSection: {
|
titleSection: {
|
||||||
paddingBottom: 30,
|
paddingBottom: 30,
|
||||||
@@ -190,6 +159,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
cardsContainer: {
|
cardsContainer: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 16,
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ export function ProfileFeedScreenInner({
|
|||||||
_(
|
_(
|
||||||
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
logger.error('Failed up update feeds', {message: err})
|
logger.error('Failed up update feeds', {message: err})
|
||||||
}
|
}
|
||||||
@@ -231,7 +232,7 @@ export function ProfileFeedScreenInner({
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -543,6 +544,7 @@ function AboutSection({
|
|||||||
_(
|
_(
|
||||||
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
|
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
|
||||||
),
|
),
|
||||||
|
'xmark',
|
||||||
)
|
)
|
||||||
logger.error('Failed up toggle like', {message: err})
|
logger.error('Failed up toggle like', {message: err})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,7 +300,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||||||
Toast.show(_(msg`Saved to your feeds`))
|
Toast.show(_(msg`Saved to your feeds`))
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -319,7 +319,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
|||||||
await removeSavedFeed(savedFeedConfig)
|
await removeSavedFeed(savedFeedConfig)
|
||||||
Toast.show(_(msg`Removed from your feeds`))
|
Toast.show(_(msg`Removed from your feeds`))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to remove pinned list', {message: e})
|
logger.error('Failed to remove pinned list', {message: e})
|
||||||
}
|
}
|
||||||
}, [playHaptic, removeSavedFeed, _, savedFeedConfig])
|
}, [playHaptic, removeSavedFeed, _, savedFeedConfig])
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ function ListItem({
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [_, playHaptic, feed, updateSavedFeeds, resetSaveFeedsMutationState])
|
}, [_, playHaptic, feed, updateSavedFeeds, resetSaveFeedsMutationState])
|
||||||
@@ -260,7 +260,7 @@ function ListItem({
|
|||||||
index: nextIndex,
|
index: nextIndex,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to set pinned feed order', {message: e})
|
logger.error('Failed to set pinned feed order', {message: e})
|
||||||
}
|
}
|
||||||
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
||||||
@@ -286,7 +286,7 @@ function ListItem({
|
|||||||
index: nextIndex,
|
index: nextIndex,
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
|
||||||
logger.error('Failed to set pinned feed order', {message: e})
|
logger.error('Failed to set pinned feed order', {message: e})
|
||||||
}
|
}
|
||||||
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
}, [feed, isPinned, overwriteSavedFeeds, currentFeeds, _])
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function ExportCarDialog({
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('Error occurred while downloading CAR file', {message: e})
|
logger.error('Error occurred while downloading CAR file', {message: e})
|
||||||
Toast.show(_(msg`Error occurred while saving file`))
|
Toast.show(_(msg`Error occurred while saving file`), 'xmark')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
control.close()
|
control.close()
|
||||||
|
|||||||
@@ -34,10 +34,10 @@
|
|||||||
jsonpointer "^5.0.0"
|
jsonpointer "^5.0.0"
|
||||||
leven "^3.1.0"
|
leven "^3.1.0"
|
||||||
|
|
||||||
"@atproto/api@^0.12.23":
|
"@atproto/api@0.12.25":
|
||||||
version "0.12.23"
|
version "0.12.25"
|
||||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.23.tgz#b3409817d0b981a64f30d16e8257f0fe261338af"
|
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.12.25.tgz#9eeb51484106a5e07f89f124e505674a3574f93b"
|
||||||
integrity sha512-fgQ30u+q9smX5g41eep7fISSkSAhRkX0inc81PZ82QwcHbFkC8ePaha/KP0CoTaPWKi7EsC89Z/8BEBCJo0oBA==
|
integrity sha512-IV3vGPnDw9bmyP/JOd8YKbm8fOpRAgJpEUVnIZNVb/Vo8v+WOroOjrJxtzdHOcXTL9IEcTTyXSCc7yE7kwhN2A==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@atproto/common-web" "^0.3.0"
|
"@atproto/common-web" "^0.3.0"
|
||||||
"@atproto/lexicon" "^0.4.0"
|
"@atproto/lexicon" "^0.4.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user