diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Navigation.tsx | 6 | ||||
-rw-r--r-- | src/components/Lists.tsx | 2 | ||||
-rw-r--r-- | src/components/forms/Toggle.tsx | 3 | ||||
-rw-r--r-- | src/components/icons/Gear.tsx | 5 | ||||
-rw-r--r-- | src/lib/async/until.ts | 10 | ||||
-rw-r--r-- | src/lib/routes/types.ts | 15 | ||||
-rw-r--r-- | src/routes.ts | 1 | ||||
-rw-r--r-- | src/screens/Messages/Conversation/index.tsx | 1 | ||||
-rw-r--r-- | src/screens/Messages/List/index.tsx | 2 | ||||
-rw-r--r-- | src/screens/Messages/Settings.tsx | 2 | ||||
-rw-r--r-- | src/state/queries/notifications/feed.ts | 37 | ||||
-rw-r--r-- | src/state/queries/notifications/settings.ts | 67 | ||||
-rw-r--r-- | src/state/queries/notifications/types.ts | 1 | ||||
-rw-r--r-- | src/state/queries/notifications/util.ts | 8 | ||||
-rw-r--r-- | src/view/com/notifications/Feed.tsx | 7 | ||||
-rw-r--r-- | src/view/screens/Notifications.tsx | 117 | ||||
-rw-r--r-- | src/view/screens/NotificationsSettings.tsx | 94 |
17 files changed, 299 insertions, 79 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 9e9b49443..8646577c8 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -76,6 +76,7 @@ import {LogScreen} from './view/screens/Log' import {ModerationModlistsScreen} from './view/screens/ModerationModlists' import {NotFoundScreen} from './view/screens/NotFound' import {NotificationsScreen} from './view/screens/Notifications' +import {NotificationsSettingsScreen} from './view/screens/NotificationsSettings' import {PostLikedByScreen} from './view/screens/PostLikedBy' import {PostRepostedByScreen} from './view/screens/PostRepostedBy' import {PostThreadScreen} from './view/screens/PostThread' @@ -325,6 +326,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { options={{title: title(msg`Chat settings`), requireAuth: true}} /> <Stack.Screen + name="NotificationsSettings" + getComponent={() => NotificationsSettingsScreen} + options={{title: title(msg`Notification settings`), requireAuth: true}} + /> + <Stack.Screen name="Feeds" getComponent={() => FeedsScreen} options={{title: title(msg`Feeds`)}} diff --git a/src/components/Lists.tsx b/src/components/Lists.tsx index 3368d076f..e706e101f 100644 --- a/src/components/Lists.tsx +++ b/src/components/Lists.tsx @@ -189,7 +189,7 @@ let ListMaybePlaceholder = ({ return ( <Error title={errorTitle ?? _(msg`Oops!`)} - message={errorMessage ?? _(`Something went wrong!`)} + message={errorMessage ?? _(msg`Something went wrong!`)} onRetry={onRetry} onGoBack={onGoBack} sideBorders={sideBorders} diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index cbdbf1c60..391b1c8b7 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -2,6 +2,7 @@ import React from 'react' 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 { atoms as a, @@ -459,3 +460,5 @@ export function Radio() { </View> ) } + +export const Platform = isNative ? Switch : Checkbox diff --git a/src/components/icons/Gear.tsx b/src/components/icons/Gear.tsx deleted file mode 100644 index 980b7413b..000000000 --- a/src/components/icons/Gear.tsx +++ /dev/null @@ -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', -}) diff --git a/src/lib/async/until.ts b/src/lib/async/until.ts index db53c9218..1b7a57633 100644 --- a/src/lib/async/until.ts +++ b/src/lib/async/until.ts @@ -1,10 +1,10 @@ import {timeout} from './timeout' -export async function until( +export async function until<T>( retries: number, delay: number, - cond: (v: any, err: any) => boolean, - fn: () => Promise<any>, + cond: (v: T, err: any) => boolean, + fn: () => Promise<T>, ): Promise<boolean> { while (retries > 0) { try { @@ -13,7 +13,9 @@ export async function until( return true } } 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 } } diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index bda93fb40..fbb66c9e9 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -42,14 +42,13 @@ export type CommonNavigatorParams = { Hashtag: {tag: string; author?: string} MessagesConversation: {conversation: string; embed?: string} MessagesSettings: undefined + NotificationsSettings: undefined Feeds: undefined Start: {name: string; rkey: string} StarterPack: {name: string; rkey: string; new?: boolean} StarterPackShort: {code: string} StarterPackWizard: undefined - StarterPackEdit: { - rkey?: string - } + StarterPackEdit: {rkey?: string} } export type BottomTabNavigatorParams = CommonNavigatorParams & { @@ -69,7 +68,7 @@ export type SearchTabNavigatorParams = CommonNavigatorParams & { } export type NotificationsTabNavigatorParams = CommonNavigatorParams & { - Notifications: undefined + Notifications: {show?: 'all'} } export type MyProfileTabNavigatorParams = CommonNavigatorParams & { @@ -84,7 +83,7 @@ export type FlatNavigatorParams = CommonNavigatorParams & { Home: undefined Search: {q?: string} Feeds: undefined - Notifications: undefined + Notifications: {show?: 'all'} Hashtag: {tag: string; author?: string} Messages: {pushToConversation?: string; animation?: 'push' | 'pop'} } @@ -96,7 +95,7 @@ export type AllNavigatorParams = CommonNavigatorParams & { Search: {q?: string} Feeds: undefined NotificationsTab: undefined - Notifications: undefined + Notifications: {show?: 'all'} MyProfileTab: undefined Hashtag: {tag: string; author?: string} MessagesTab: undefined @@ -105,9 +104,7 @@ export type AllNavigatorParams = CommonNavigatorParams & { StarterPack: {name: string; rkey: string; new?: boolean} StarterPackShort: {code: string} StarterPackWizard: undefined - StarterPackEdit: { - rkey?: string - } + StarterPackEdit: {rkey?: string} } // NOTE diff --git a/src/routes.ts b/src/routes.ts index a76d8c4ce..ddf4fb39f 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -5,6 +5,7 @@ export const router = new Router({ Search: '/search', Feeds: '/feeds', Notifications: '/notifications', + NotificationsSettings: '/notifications/settings', Settings: '/settings', LanguageSettings: '/settings/language', Lists: '/lists', diff --git a/src/screens/Messages/Conversation/index.tsx b/src/screens/Messages/Conversation/index.tsx index a99ef8d4d..d14ed160a 100644 --- a/src/screens/Messages/Conversation/index.tsx +++ b/src/screens/Messages/Conversation/index.tsx @@ -106,6 +106,7 @@ function Inner() { title={_(msg`Something went wrong`)} message={_(msg`We couldn't load this conversation`)} onRetry={() => convoState.error.retry()} + sideBorders={false} /> </CenteredView> ) diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/List/index.tsx index 0b1fe2a95..2fd9990c7 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/List/index.tsx @@ -309,7 +309,7 @@ function DesktopHeader({ a.gap_lg, a.px_lg, a.pr_md, - a.py_md, + a.py_sm, a.border_b, t.atoms.border_contrast_low, ]}> diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index 3d7e60130..df469d13f 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -107,7 +107,7 @@ export function MessagesSettingsScreen({}: Props) { a.rounded_md, t.atoms.bg_contrast_25, ]}> - <Text style={[t.atoms.text_contrast_high]}> + <Text style={[t.atoms.text_contrast_high, a.leading_snug]}> <Trans> You can continue ongoing conversations regardless of which setting you choose. diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 17ee90929..3cafcb716 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -46,11 +46,14 @@ const PAGE_SIZE = 30 type RQPageParam = string | undefined const RQKEY_ROOT = 'notification-feed' -export function RQKEY() { - return [RQKEY_ROOT] +export function RQKEY(priority?: false) { + return [RQKEY_ROOT, priority] } -export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { +export function useNotificationFeedQuery(opts?: { + enabled?: boolean + overridePriorityNotifications?: boolean +}) { const agent = useAgent() const queryClient = useQueryClient() const moderationOpts = useModerationOpts() @@ -59,6 +62,10 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { const lastPageCountRef = useRef(0) const gate = useGate() + // false: force showing all notifications + // undefined: let the server decide + const priority = opts?.overridePriorityNotifications ? false : undefined + const query = useInfiniteQuery< FeedPage, Error, @@ -67,7 +74,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { RQPageParam >({ staleTime: STALE.INFINITY, - queryKey: RQKEY(), + queryKey: RQKEY(priority), async queryFn({pageParam}: {pageParam: RQPageParam}) { let page if (!pageParam) { @@ -75,17 +82,17 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) { page = unreads.getCachedUnreadPage() } if (!page) { - page = ( - await fetchPage({ - agent, - limit: PAGE_SIZE, - cursor: pageParam, - queryClient, - moderationOpts, - fetchAdditionalData: true, - shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), - }) - ).page + const {page: fetchedPage} = await fetchPage({ + agent, + limit: PAGE_SIZE, + cursor: pageParam, + queryClient, + moderationOpts, + fetchAdditionalData: true, + shouldUngroupFollowBacks: () => gate('ungroup_follow_backs'), + priority, + }) + page = fetchedPage } // if the first page has an unread, mark all read diff --git a/src/state/queries/notifications/settings.ts b/src/state/queries/notifications/settings.ts new file mode 100644 index 000000000..78ecbd9f7 --- /dev/null +++ b/src/state/queries/notifications/settings.ts @@ -0,0 +1,67 @@ +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 {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: () => { + 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, + } + }), + } + }) +} diff --git a/src/state/queries/notifications/types.ts b/src/state/queries/notifications/types.ts index d40a07b12..c96374eb8 100644 --- a/src/state/queries/notifications/types.ts +++ b/src/state/queries/notifications/types.ts @@ -22,6 +22,7 @@ export interface FeedPage { cursor: string | undefined seenAt: Date items: FeedNotification[] + priority: boolean } export interface CachedFeedPage { diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 2f2c242d8..7651e414a 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -39,10 +39,15 @@ export async function fetchPage({ moderationOpts: ModerationOpts | undefined fetchAdditionalData: boolean shouldUngroupFollowBacks?: () => boolean -}): Promise<{page: FeedPage; indexedAt: string | undefined}> { + priority?: boolean +}): Promise<{ + page: FeedPage + indexedAt: string | undefined +}> { const res = await agent.listNotifications({ limit, cursor, + // priority, }) const indexedAt = res.data.notifications[0]?.indexedAt @@ -88,6 +93,7 @@ export async function fetchPage({ cursor: res.data.cursor, seenAt, items: notifsGrouped, + priority: res.data.priority ?? false, }, indexedAt, } diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx index e2f12e84f..3e7fdfc71 100644 --- a/src/view/com/notifications/Feed.tsx +++ b/src/view/com/notifications/Feed.tsx @@ -35,11 +35,13 @@ export function Feed({ onPressTryAgain, onScrolledDownChange, ListHeaderComponent, + overridePriorityNotifications, }: { scrollElRef?: ListRef onPressTryAgain?: () => void onScrolledDownChange: (isScrolledDown: boolean) => void ListHeaderComponent?: () => JSX.Element + overridePriorityNotifications?: boolean }) { const initialNumToRender = useInitialNumToRender() @@ -59,7 +61,10 @@ export function Feed({ hasNextPage, isFetchingNextPage, fetchNextPage, - } = useNotificationFeedQuery({enabled: !!moderationOpts}) + } = useNotificationFeedQuery({ + enabled: !!moderationOpts, + overridePriorityNotifications, + }) const isEmpty = !isFetching && !data?.pages[0]?.items.length const items = React.useMemo(() => { diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index f1ae7945a..073e91c45 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -1,11 +1,19 @@ -import React from 'react' +import React, {useCallback} from 'react' import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useIsFocused} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' +import {useAnalytics} from '#/lib/analytics/analytics' 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 {isNative} from '#/platform/detection' import {emitSoftReset, listenSoftReset} from '#/state/events' @@ -17,37 +25,32 @@ import { import {truncateAndInvalidate} from '#/state/queries/util' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ComposeIcon2} from 'lib/icons' -import { - NativeStackScreenProps, - NotificationsTabNavigatorParams, -} from 'lib/routes/types' -import {colors, s} from 'lib/styles' -import {TextLink} from 'view/com/util/Link' +import {Feed} from '#/view/com/notifications/Feed' +import {FAB} from '#/view/com/util/fab/FAB' +import {MainScrollProvider} from '#/view/com/util/MainScrollProvider' +import {ViewHeader} from '#/view/com/util/ViewHeader' import {ListMethods} from 'view/com/util/List' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' 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 {Feed} from '../com/notifications/Feed' -import {FAB} from '../com/util/fab/FAB' -import {MainScrollProvider} from '../com/util/MainScrollProvider' -import {ViewHeader} from '../com/util/ViewHeader' +import {Text} from '#/components/Typography' type Props = NativeStackScreenProps< NotificationsTabNavigatorParams, 'Notifications' > -export function NotificationsScreen({}: Props) { +export function NotificationsScreen({route: {params}}: Props) { const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isLoadingLatest, setIsLoadingLatest] = React.useState(false) const scrollElRef = React.useRef<ListMethods>(null) const {screen} = useAnalytics() - const pal = usePalette('default') + const t = useTheme() const {isDesktop} = useWebMediaQueries() const queryClient = useQueryClient() const unreadNotifs = useUnreadNotifications() @@ -109,56 +112,87 @@ export function NotificationsScreen({}: Props) { return listenSoftReset(onPressLoadLatest) }, [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(() => { if (isDesktop) { return ( <View style={[ - pal.view, - { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 18, - paddingVertical: 12, - }, + t.atoms.bg, + a.flex_row, + a.align_center, + a.justify_between, + a.gap_lg, + a.px_lg, + a.pr_md, + a.py_sm, ]}> - <TextLink - type="title-lg" - href="/notifications" - style={[pal.text, {fontWeight: 'bold'}]} - text={ - <> - <Trans>Notifications</Trans>{' '} + <Button + label={_(msg`Notifications`)} + accessibilityHint={_(msg`Refresh notifications`)} + onPress={emitSoftReset}> + {({hovered, pressed}) => ( + <Text + style={[ + a.text_2xl, + a.font_bold, + (hovered || pressed) && a.underline, + ]}> + <Trans>Notifications</Trans> {hasNew && ( <View style={{ + left: 4, top: -8, - backgroundColor: colors.blue3, + backgroundColor: t.palette.primary_500, width: 8, height: 8, borderRadius: 4, }} /> )} - </> - } - onPress={emitSoftReset} - /> - {isLoadingLatest ? <Loader size="md" /> : <></>} + </Text> + )} + </Button> + <View style={[a.flex_row, a.align_center, a.gap_sm]}> + {isLoadingLatest ? <Loader size="md" /> : <></>} + {renderButton()} + </View> </View> ) } return <></> - }, [isDesktop, pal, hasNew, isLoadingLatest]) + }, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest]) const renderHeaderSpinner = React.useCallback(() => { 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} /> : <></>} + {renderButton()} </View> ) - }, [isLoadingLatest]) + }, [renderButton, isLoadingLatest]) return ( <CenteredView @@ -176,6 +210,7 @@ export function NotificationsScreen({}: Props) { onScrolledDownChange={setIsScrolledDown} scrollElRef={scrollElRef} ListHeaderComponent={ListHeaderComponent} + overridePriorityNotifications={params?.show === 'all'} /> </MainScrollProvider> {(isScrolledDown || hasNew) && ( diff --git a/src/view/screens/NotificationsSettings.tsx b/src/view/screens/NotificationsSettings.tsx new file mode 100644 index 000000000..2716a07f9 --- /dev/null +++ b/src/view/screens/NotificationsSettings.tsx @@ -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 {CenteredView} 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 ( + <CenteredView style={a.flex_1} sideBorders> + <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> + )} + </CenteredView> + ) +} |