Merge remote-tracking branch 'upstream/main' into Improve-notification-localization
This commit is contained in:
+4
-4
@@ -23,7 +23,7 @@ class ExpoBlueskyReferrerModule : Module() {
|
|||||||
activityReferrer = appContext.currentActivity?.referrer
|
activityReferrer = appContext.currentActivity?.referrer
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncFunction("getReferrerInfoAsync") {
|
Function("getReferrerInfo") {
|
||||||
val intentReferrer =
|
val intentReferrer =
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
intent?.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
|
intent?.getParcelableExtra(Intent.EXTRA_REFERRER, Uri::class.java)
|
||||||
@@ -40,7 +40,7 @@ class ExpoBlueskyReferrerModule : Module() {
|
|||||||
"hostname" to intentReferrer.host,
|
"hostname" to intentReferrer.host,
|
||||||
)
|
)
|
||||||
intent = null
|
intent = null
|
||||||
return@AsyncFunction res
|
return@Function res
|
||||||
}
|
}
|
||||||
|
|
||||||
// In all other cases, we'll just record the app that sent the intent.
|
// In all other cases, we'll just record the app that sent the intent.
|
||||||
@@ -52,10 +52,10 @@ class ExpoBlueskyReferrerModule : Module() {
|
|||||||
"hostname" to (activityReferrer?.host ?: ""),
|
"hostname" to (activityReferrer?.host ?: ""),
|
||||||
)
|
)
|
||||||
activityReferrer = null
|
activityReferrer = null
|
||||||
return@AsyncFunction res
|
return@Function res
|
||||||
}
|
}
|
||||||
|
|
||||||
return@AsyncFunction null
|
return@Function null
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise ->
|
AsyncFunction("getGooglePlayReferrerInfoAsync") { promise: Promise ->
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
return NativeModule.getGooglePlayReferrerInfoAsync()
|
return NativeModule.getGooglePlayReferrerInfoAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
|
export function getReferrerInfo(): Promise<ReferrerInfo | null> {
|
||||||
return NativeModule.getReferrerInfoAsync()
|
return NativeModule.getReferrerInfo()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
|
export function getReferrerInfo(): ReferrerInfo | null {
|
||||||
const referrer = SharedPrefs.getString('referrer')
|
const referrer = SharedPrefs.getString('referrer')
|
||||||
if (referrer) {
|
if (referrer) {
|
||||||
SharedPrefs.removeValue('referrer')
|
SharedPrefs.removeValue('referrer')
|
||||||
@@ -19,7 +19,7 @@ export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
return {
|
return {
|
||||||
referrer,
|
referrer,
|
||||||
hostname: undefined,
|
hostname: referrer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
|
export function getReferrerInfo(): ReferrerInfo | null {
|
||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function getGooglePlayReferrerInfoAsync(): Promise<GooglePlayReferrerInfo
|
|||||||
throw new NotImplementedError()
|
throw new NotImplementedError()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReferrerInfoAsync(): Promise<ReferrerInfo | null> {
|
export function getReferrerInfo(): ReferrerInfo | null {
|
||||||
if (
|
if (
|
||||||
Platform.OS === 'web' &&
|
Platform.OS === 'web' &&
|
||||||
// for ssr
|
// for ssr
|
||||||
|
|||||||
+8
-9
@@ -771,15 +771,14 @@ function logModuleInitTime() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
Referrer.getReferrerInfoAsync().then(info => {
|
const referrerInfo = Referrer.getReferrerInfo()
|
||||||
if (info && info.hostname !== 'bsky.app') {
|
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
||||||
logEvent('deepLink:referrerReceived', {
|
logEvent('deepLink:referrerReceived', {
|
||||||
to: window.location.href,
|
to: window.location.href,
|
||||||
referrer: info?.referrer,
|
referrer: referrerInfo?.referrer,
|
||||||
hostname: info?.hostname,
|
hostname: referrerInfo?.hostname,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
|
|||||||
+26
-26
@@ -261,23 +261,23 @@ export function createThemes({
|
|||||||
contrast_950: `hsl(${hues.primary}, 20%, ${dimScale[12]}%)`,
|
contrast_950: `hsl(${hues.primary}, 20%, ${dimScale[12]}%)`,
|
||||||
contrast_975: `hsl(${hues.primary}, 20%, ${dimScale[13]}%)`,
|
contrast_975: `hsl(${hues.primary}, 20%, ${dimScale[13]}%)`,
|
||||||
|
|
||||||
primary_25: `hsl(${hues.primary}, 99%, ${dimScale[1]}%)`,
|
primary_25: `hsl(${hues.primary}, 50%, ${dimScale[1]}%)`,
|
||||||
primary_50: `hsl(${hues.primary}, 99%, ${dimScale[2]}%)`,
|
primary_50: `hsl(${hues.primary}, 60%, ${dimScale[2]}%)`,
|
||||||
primary_100: `hsl(${hues.primary}, 99%, ${dimScale[3]}%)`,
|
primary_100: `hsl(${hues.primary}, 70%, ${dimScale[3]}%)`,
|
||||||
primary_200: `hsl(${hues.primary}, 99%, ${dimScale[4]}%)`,
|
primary_200: `hsl(${hues.primary}, 82%, ${dimScale[4]}%)`,
|
||||||
primary_300: `hsl(${hues.primary}, 99%, ${dimScale[5]}%)`,
|
primary_300: `hsl(${hues.primary}, 90%, ${dimScale[5]}%)`,
|
||||||
primary_400: `hsl(${hues.primary}, 99%, ${dimScale[6]}%)`,
|
primary_400: `hsl(${hues.primary}, 95%, ${dimScale[6]}%)`,
|
||||||
primary_500: `hsl(${hues.primary}, 99%, ${dimScale[7]}%)`,
|
primary_500: `hsl(${hues.primary}, 99%, ${dimScale[7]}%)`,
|
||||||
primary_600: `hsl(${hues.primary}, 95%, ${dimScale[8]}%)`,
|
primary_600: `hsl(${hues.primary}, 99%, ${dimScale[8]}%)`,
|
||||||
primary_700: `hsl(${hues.primary}, 90%, ${dimScale[9]}%)`,
|
primary_700: `hsl(${hues.primary}, 99%, ${dimScale[9]}%)`,
|
||||||
primary_800: `hsl(${hues.primary}, 82%, ${dimScale[10]}%)`,
|
primary_800: `hsl(${hues.primary}, 99%, ${dimScale[10]}%)`,
|
||||||
primary_900: `hsl(${hues.primary}, 70%, ${dimScale[11]}%)`,
|
primary_900: `hsl(${hues.primary}, 99%, ${dimScale[11]}%)`,
|
||||||
primary_950: `hsl(${hues.primary}, 60%, ${dimScale[12]}%)`,
|
primary_950: `hsl(${hues.primary}, 99%, ${dimScale[12]}%)`,
|
||||||
primary_975: `hsl(${hues.primary}, 50%, ${dimScale[13]}%)`,
|
primary_975: `hsl(${hues.primary}, 99%, ${dimScale[13]}%)`,
|
||||||
|
|
||||||
positive_25: `hsl(${hues.positive}, 82%, ${dimScale[1]}%)`,
|
positive_25: `hsl(${hues.positive}, 50%, ${dimScale[1]}%)`,
|
||||||
positive_50: `hsl(${hues.positive}, 82%, ${dimScale[2]}%)`,
|
positive_50: `hsl(${hues.positive}, 60%, ${dimScale[2]}%)`,
|
||||||
positive_100: `hsl(${hues.positive}, 82%, ${dimScale[3]}%)`,
|
positive_100: `hsl(${hues.positive}, 70%, ${dimScale[3]}%)`,
|
||||||
positive_200: `hsl(${hues.positive}, 82%, ${dimScale[4]}%)`,
|
positive_200: `hsl(${hues.positive}, 82%, ${dimScale[4]}%)`,
|
||||||
positive_300: `hsl(${hues.positive}, 82%, ${dimScale[5]}%)`,
|
positive_300: `hsl(${hues.positive}, 82%, ${dimScale[5]}%)`,
|
||||||
positive_400: `hsl(${hues.positive}, 82%, ${dimScale[6]}%)`,
|
positive_400: `hsl(${hues.positive}, 82%, ${dimScale[6]}%)`,
|
||||||
@@ -285,23 +285,23 @@ export function createThemes({
|
|||||||
positive_600: `hsl(${hues.positive}, 82%, ${dimScale[8]}%)`,
|
positive_600: `hsl(${hues.positive}, 82%, ${dimScale[8]}%)`,
|
||||||
positive_700: `hsl(${hues.positive}, 82%, ${dimScale[9]}%)`,
|
positive_700: `hsl(${hues.positive}, 82%, ${dimScale[9]}%)`,
|
||||||
positive_800: `hsl(${hues.positive}, 82%, ${dimScale[10]}%)`,
|
positive_800: `hsl(${hues.positive}, 82%, ${dimScale[10]}%)`,
|
||||||
positive_900: `hsl(${hues.positive}, 70%, ${dimScale[11]}%)`,
|
positive_900: `hsl(${hues.positive}, 82%, ${dimScale[11]}%)`,
|
||||||
positive_950: `hsl(${hues.positive}, 60%, ${dimScale[12]}%)`,
|
positive_950: `hsl(${hues.positive}, 82%, ${dimScale[12]}%)`,
|
||||||
positive_975: `hsl(${hues.positive}, 50%, ${dimScale[13]}%)`,
|
positive_975: `hsl(${hues.positive}, 82%, ${dimScale[13]}%)`,
|
||||||
|
|
||||||
negative_25: `hsl(${hues.negative}, 91%, ${dimScale[1]}%)`,
|
negative_25: `hsl(${hues.negative}, 70%, ${dimScale[1]}%)`,
|
||||||
negative_50: `hsl(${hues.negative}, 91%, ${dimScale[2]}%)`,
|
negative_50: `hsl(${hues.negative}, 80%, ${dimScale[2]}%)`,
|
||||||
negative_100: `hsl(${hues.negative}, 91%, ${dimScale[3]}%)`,
|
negative_100: `hsl(${hues.negative}, 84%, ${dimScale[3]}%)`,
|
||||||
negative_200: `hsl(${hues.negative}, 91%, ${dimScale[4]}%)`,
|
negative_200: `hsl(${hues.negative}, 88%, ${dimScale[4]}%)`,
|
||||||
negative_300: `hsl(${hues.negative}, 91%, ${dimScale[5]}%)`,
|
negative_300: `hsl(${hues.negative}, 91%, ${dimScale[5]}%)`,
|
||||||
negative_400: `hsl(${hues.negative}, 91%, ${dimScale[6]}%)`,
|
negative_400: `hsl(${hues.negative}, 91%, ${dimScale[6]}%)`,
|
||||||
negative_500: `hsl(${hues.negative}, 91%, ${dimScale[7]}%)`,
|
negative_500: `hsl(${hues.negative}, 91%, ${dimScale[7]}%)`,
|
||||||
negative_600: `hsl(${hues.negative}, 91%, ${dimScale[8]}%)`,
|
negative_600: `hsl(${hues.negative}, 91%, ${dimScale[8]}%)`,
|
||||||
negative_700: `hsl(${hues.negative}, 91%, ${dimScale[9]}%)`,
|
negative_700: `hsl(${hues.negative}, 91%, ${dimScale[9]}%)`,
|
||||||
negative_800: `hsl(${hues.negative}, 88%, ${dimScale[10]}%)`,
|
negative_800: `hsl(${hues.negative}, 91%, ${dimScale[10]}%)`,
|
||||||
negative_900: `hsl(${hues.negative}, 84%, ${dimScale[11]}%)`,
|
negative_900: `hsl(${hues.negative}, 91%, ${dimScale[11]}%)`,
|
||||||
negative_950: `hsl(${hues.negative}, 80%, ${dimScale[12]}%)`,
|
negative_950: `hsl(${hues.negative}, 91%, ${dimScale[12]}%)`,
|
||||||
negative_975: `hsl(${hues.negative}, 70%, ${dimScale[13]}%)`,
|
negative_975: `hsl(${hues.negative}, 91%, ${dimScale[13]}%)`,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const light: Theme = {
|
const light: Theme = {
|
||||||
|
|||||||
@@ -133,3 +133,5 @@ export const GIF_SEARCH = (params: string) =>
|
|||||||
`${GIF_SERVICE}/tenor/v2/search?${params}`
|
`${GIF_SERVICE}/tenor/v2/search?${params}`
|
||||||
export const GIF_FEATURED = (params: string) =>
|
export const GIF_FEATURED = (params: string) =>
|
||||||
`${GIF_SERVICE}/tenor/v2/featured?${params}`
|
`${GIF_SERVICE}/tenor/v2/featured?${params}`
|
||||||
|
|
||||||
|
export const MAX_LABELERS = 20
|
||||||
|
|||||||
@@ -18,15 +18,14 @@ export function useIntentHandler() {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const handleIncomingURL = (url: string) => {
|
const handleIncomingURL = (url: string) => {
|
||||||
Referrer.getReferrerInfoAsync().then(info => {
|
const referrerInfo = Referrer.getReferrerInfo()
|
||||||
if (info && info.hostname !== 'bsky.app') {
|
if (referrerInfo && referrerInfo.hostname !== 'bsky.app') {
|
||||||
logEvent('deepLink:referrerReceived', {
|
logEvent('deepLink:referrerReceived', {
|
||||||
to: url,
|
to: url,
|
||||||
referrer: info?.referrer,
|
referrer: referrerInfo?.referrer,
|
||||||
hostname: info?.hostname,
|
hostname: referrerInfo?.hostname,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
// We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
|
// We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
|
||||||
// slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
|
// slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
import {msg, Plural, plural, Trans} from '@lingui/macro'
|
import {msg, Plural, plural, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import {MAX_LABELERS} from '#/lib/constants'
|
||||||
import {isAppLabeler} from '#/lib/moderation'
|
import {isAppLabeler} from '#/lib/moderation'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Shadow} from '#/state/cache/types'
|
import {Shadow} from '#/state/cache/types'
|
||||||
@@ -75,14 +77,14 @@ let ProfileHeaderLabeler = ({
|
|||||||
[profile, moderationOpts],
|
[profile, moderationOpts],
|
||||||
)
|
)
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {mutateAsync: toggleSubscription, variables} =
|
const {
|
||||||
useLabelerSubscriptionMutation()
|
mutateAsync: toggleSubscription,
|
||||||
|
variables,
|
||||||
|
reset,
|
||||||
|
} = useLabelerSubscriptionMutation()
|
||||||
const isSubscribed =
|
const isSubscribed =
|
||||||
variables?.subscribe ??
|
variables?.subscribe ??
|
||||||
preferences?.moderationPrefs.labelers.find(l => l.did === profile.did)
|
preferences?.moderationPrefs.labelers.find(l => l.did === profile.did)
|
||||||
const canSubscribe =
|
|
||||||
isSubscribed ||
|
|
||||||
(preferences ? preferences?.moderationPrefs.labelers.length <= 20 : false)
|
|
||||||
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
|
const {mutateAsync: likeMod, isPending: isLikePending} = useLikeMutation()
|
||||||
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
|
const {mutateAsync: unlikeMod, isPending: isUnlikePending} =
|
||||||
useUnlikeMutation()
|
useUnlikeMutation()
|
||||||
@@ -130,17 +132,17 @@ let ProfileHeaderLabeler = ({
|
|||||||
const onPressSubscribe = React.useCallback(
|
const onPressSubscribe = React.useCallback(
|
||||||
() =>
|
() =>
|
||||||
requireAuth(async (): Promise<void> => {
|
requireAuth(async (): Promise<void> => {
|
||||||
if (!canSubscribe) {
|
|
||||||
cantSubscribePrompt.open()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await toggleSubscription({
|
await toggleSubscription({
|
||||||
did: profile.did,
|
did: profile.did,
|
||||||
subscribe: !isSubscribed,
|
subscribe: !isSubscribed,
|
||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
// setSubscriptionError(e.message)
|
reset()
|
||||||
|
if (e.message === 'MAX_LABELERS') {
|
||||||
|
cantSubscribePrompt.open()
|
||||||
|
return
|
||||||
|
}
|
||||||
logger.error(`Failed to subscribe to labeler`, {message: e.message})
|
logger.error(`Failed to subscribe to labeler`, {message: e.message})
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@@ -149,8 +151,8 @@ let ProfileHeaderLabeler = ({
|
|||||||
toggleSubscription,
|
toggleSubscription,
|
||||||
isSubscribed,
|
isSubscribed,
|
||||||
profile,
|
profile,
|
||||||
canSubscribe,
|
|
||||||
cantSubscribePrompt,
|
cantSubscribePrompt,
|
||||||
|
reset,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,14 +201,13 @@ let ProfileHeaderLabeler = ({
|
|||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
paddingVertical: gtMobile ? 12 : 10,
|
paddingVertical: gtMobile ? 12 : 10,
|
||||||
backgroundColor:
|
backgroundColor: isSubscribed
|
||||||
isSubscribed || !canSubscribe
|
? state.hovered || state.pressed
|
||||||
? state.hovered || state.pressed
|
? t.palette.contrast_50
|
||||||
? t.palette.contrast_50
|
: t.palette.contrast_25
|
||||||
: t.palette.contrast_25
|
: state.hovered || state.pressed
|
||||||
: state.hovered || state.pressed
|
? tokens.color.temp_purple_dark
|
||||||
? tokens.color.temp_purple_dark
|
: tokens.color.temp_purple,
|
||||||
: tokens.color.temp_purple,
|
|
||||||
},
|
},
|
||||||
a.px_lg,
|
a.px_lg,
|
||||||
a.rounded_sm,
|
a.rounded_sm,
|
||||||
@@ -215,11 +216,9 @@ let ProfileHeaderLabeler = ({
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
{
|
{
|
||||||
color: canSubscribe
|
color: isSubscribed
|
||||||
? isSubscribed
|
? t.palette.contrast_700
|
||||||
? t.palette.contrast_700
|
: t.palette.white,
|
||||||
: t.palette.white
|
|
||||||
: t.palette.contrast_400,
|
|
||||||
},
|
},
|
||||||
a.font_bold,
|
a.font_bold,
|
||||||
a.text_center,
|
a.text_center,
|
||||||
@@ -317,6 +316,9 @@ let ProfileHeaderLabeler = ({
|
|||||||
ProfileHeaderLabeler = memo(ProfileHeaderLabeler)
|
ProfileHeaderLabeler = memo(ProfileHeaderLabeler)
|
||||||
export {ProfileHeaderLabeler}
|
export {ProfileHeaderLabeler}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep this in sync with the value of {@link MAX_LABELERS}
|
||||||
|
*/
|
||||||
function CantSubscribePrompt({
|
function CantSubscribePrompt({
|
||||||
control,
|
control,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -2,9 +2,13 @@ import {AppBskyLabelerDefs} from '@atproto/api'
|
|||||||
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
|
||||||
import {z} from 'zod'
|
import {z} from 'zod'
|
||||||
|
|
||||||
|
import {MAX_LABELERS} from '#/lib/constants'
|
||||||
import {labelersDetailedInfoQueryKeyRoot} from '#/lib/react-query'
|
import {labelersDetailedInfoQueryKeyRoot} from '#/lib/react-query'
|
||||||
import {STALE} from '#/state/queries'
|
import {STALE} from '#/state/queries'
|
||||||
import {preferencesQueryKey} from '#/state/queries/preferences'
|
import {
|
||||||
|
preferencesQueryKey,
|
||||||
|
usePreferencesQuery,
|
||||||
|
} from '#/state/queries/preferences'
|
||||||
import {useAgent} from '#/state/session'
|
import {useAgent} from '#/state/session'
|
||||||
|
|
||||||
const labelerInfoQueryKeyRoot = 'labeler-info'
|
const labelerInfoQueryKeyRoot = 'labeler-info'
|
||||||
@@ -77,6 +81,7 @@ export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
|
|||||||
export function useLabelerSubscriptionMutation() {
|
export function useLabelerSubscriptionMutation() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const agent = useAgent()
|
const agent = useAgent()
|
||||||
|
const preferences = usePreferencesQuery()
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
|
async mutationFn({did, subscribe}: {did: string; subscribe: boolean}) {
|
||||||
@@ -86,14 +91,52 @@ export function useLabelerSubscriptionMutation() {
|
|||||||
subscribe: z.boolean(),
|
subscribe: z.boolean(),
|
||||||
}).parse({did, subscribe})
|
}).parse({did, subscribe})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a user has invalid/takendown/deactivated labelers, we need to
|
||||||
|
* remove them. We don't have a great way to do this atm on the server,
|
||||||
|
* so we do it here.
|
||||||
|
*
|
||||||
|
* We also need to push validation into this method, since we need to
|
||||||
|
* check {@link MAX_LABELERS} _after_ we've removed invalid or takendown
|
||||||
|
* labelers.
|
||||||
|
*/
|
||||||
|
const labelerDids = (
|
||||||
|
preferences.data?.moderationPrefs?.labelers ?? []
|
||||||
|
).map(l => l.did)
|
||||||
|
const invalidLabelers: string[] = []
|
||||||
|
if (labelerDids.length) {
|
||||||
|
const profiles = await agent.getProfiles({actors: labelerDids})
|
||||||
|
if (profiles.data) {
|
||||||
|
for (const did of labelerDids) {
|
||||||
|
const exists = profiles.data.profiles.find(p => p.did === did)
|
||||||
|
if (exists) {
|
||||||
|
// profile came back but it's not a valid labeler
|
||||||
|
if (exists.associated && !exists.associated.labeler) {
|
||||||
|
invalidLabelers.push(did)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no response came back, might be deactivated or takendown
|
||||||
|
invalidLabelers.push(did)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (invalidLabelers.length) {
|
||||||
|
await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did)))
|
||||||
|
}
|
||||||
|
|
||||||
if (subscribe) {
|
if (subscribe) {
|
||||||
|
const labelerCount = labelerDids.length - invalidLabelers.length
|
||||||
|
if (labelerCount >= MAX_LABELERS) {
|
||||||
|
throw new Error('MAX_LABELERS')
|
||||||
|
}
|
||||||
await agent.addLabeler(did)
|
await agent.addLabeler(did)
|
||||||
} else {
|
} else {
|
||||||
await agent.removeLabeler(did)
|
await agent.removeLabeler(did)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSuccess() {
|
async onSuccess() {
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: preferencesQueryKey,
|
queryKey: preferencesQueryKey,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
+24
-34
@@ -9,10 +9,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {
|
import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
|
||||||
FontAwesomeIcon,
|
|
||||||
FontAwesomeIconStyle,
|
|
||||||
} 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 {StackActions, useNavigation} from '@react-navigation/native'
|
import {StackActions, useNavigation} from '@react-navigation/native'
|
||||||
@@ -37,6 +34,7 @@ import {formatCountShortOnly} from 'view/com/util/numeric/format'
|
|||||||
import {Text} from 'view/com/util/text/Text'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import {useTheme as useAlfTheme} from '#/alf'
|
import {useTheme as useAlfTheme} from '#/alf'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
import {
|
import {
|
||||||
Bell_Filled_Corner0_Rounded as BellFilled,
|
Bell_Filled_Corner0_Rounded as BellFilled,
|
||||||
Bell_Stroke2_Corner0_Rounded as Bell,
|
Bell_Stroke2_Corner0_Rounded as Bell,
|
||||||
@@ -52,6 +50,7 @@ import {
|
|||||||
} from '#/components/icons/HomeOpen'
|
} from '#/components/icons/HomeOpen'
|
||||||
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
import {MagnifyingGlass_Filled_Stroke2_Corner0_Rounded as MagnifyingGlassFilled} from '#/components/icons/MagnifyingGlass'
|
||||||
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
import {MagnifyingGlass2_Stroke2_Corner0_Rounded as MagnifyingGlass} from '#/components/icons/MagnifyingGlass2'
|
||||||
|
import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message'
|
||||||
import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
|
import {SettingsGear2_Stroke2_Corner0_Rounded as Settings} from '#/components/icons/SettingsGear2'
|
||||||
import {
|
import {
|
||||||
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
|
UserCircle_Filled_Corner0_Rounded as UserCircleFilled,
|
||||||
@@ -319,42 +318,33 @@ let DrawerFooter = ({
|
|||||||
onPressFeedback: () => void
|
onPressFeedback: () => void
|
||||||
onPressHelp: () => void
|
onPressHelp: () => void
|
||||||
}): React.ReactNode => {
|
}): React.ReactNode => {
|
||||||
const theme = useTheme()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
return (
|
return (
|
||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<TouchableOpacity
|
<Button
|
||||||
accessibilityRole="link"
|
label={_(msg`Send feedback`)}
|
||||||
accessibilityLabel={_(msg`Send feedback`)}
|
size="small"
|
||||||
accessibilityHint=""
|
variant="solid"
|
||||||
onPress={onPressFeedback}
|
color="secondary"
|
||||||
style={[
|
onPress={onPressFeedback}>
|
||||||
styles.footerBtn,
|
<ButtonIcon icon={Message} position="left" />
|
||||||
styles.footerBtnFeedback,
|
<ButtonText>
|
||||||
theme.colorScheme === 'light'
|
|
||||||
? styles.footerBtnFeedbackLight
|
|
||||||
: styles.footerBtnFeedbackDark,
|
|
||||||
]}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
style={pal.link as FontAwesomeIconStyle}
|
|
||||||
size={18}
|
|
||||||
icon={['far', 'message']}
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={[pal.link, s.pl10]}>
|
|
||||||
<Trans>Feedback</Trans>
|
<Trans>Feedback</Trans>
|
||||||
</Text>
|
</ButtonText>
|
||||||
</TouchableOpacity>
|
</Button>
|
||||||
<TouchableOpacity
|
<Button
|
||||||
accessibilityRole="link"
|
label={_(msg`Get help`)}
|
||||||
accessibilityLabel={_(msg`Send feedback`)}
|
size="small"
|
||||||
accessibilityHint=""
|
variant="outline"
|
||||||
|
color="secondary"
|
||||||
onPress={onPressHelp}
|
onPress={onPressHelp}
|
||||||
style={[styles.footerBtn]}>
|
style={{
|
||||||
<Text type="lg-medium" style={[pal.link, s.pl10]}>
|
backgroundColor: 'transparent',
|
||||||
|
}}>
|
||||||
|
<ButtonText>
|
||||||
<Trans>Help</Trans>
|
<Trans>Help</Trans>
|
||||||
</Text>
|
</ButtonText>
|
||||||
</TouchableOpacity>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user