From 7b413d05879fae18dc166a0dbb3fa7f4f4e6c249 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 10:09:09 -0700 Subject: [PATCH 1/7] improve android haptics, offer toggle for haptics --- patches/expo-haptics+12.8.1.md | 11 ++ patches/expo-haptics+12.8.1.patch | 13 +++ src/lib/haptics.ts | 20 ++-- .../Profile/Header/ProfileHeaderLabeler.tsx | 6 +- src/state/persisted/legacy.ts | 3 +- src/state/persisted/schema.ts | 3 + src/state/preferences/disable-haptics.tsx | 42 +++++++ src/state/preferences/index.tsx | 10 +- src/view/com/util/post-ctrls/PostCtrls.tsx | 23 ++-- src/view/screens/ProfileFeed.tsx | 39 +++++-- src/view/screens/ProfileList.tsx | 107 +++++++++--------- src/view/screens/SavedFeeds.tsx | 53 +++++---- src/view/screens/Settings/index.tsx | 84 ++++---------- src/view/shell/bottom-bar/BottomBar.tsx | 6 +- 14 files changed, 252 insertions(+), 168 deletions(-) create mode 100644 patches/expo-haptics+12.8.1.md create mode 100644 patches/expo-haptics+12.8.1.patch create mode 100644 src/state/preferences/disable-haptics.tsx diff --git a/patches/expo-haptics+12.8.1.md b/patches/expo-haptics+12.8.1.md new file mode 100644 index 0000000000..afa7395bc0 --- /dev/null +++ b/patches/expo-haptics+12.8.1.md @@ -0,0 +1,11 @@ +# Expo Haptics Patch + +Whenever we migrated to Expo Haptics, there was a difference between how the previous and new libraries handled the +Android implementation of an iOS "light" haptic. The previous library used the `Vibration` API solely, which does not +have any configuration for intensity of vibration. The `Vibration` API has also been deprecated since SDK 26. See: +https://github.com/mkuczera/react-native-haptic-feedback/blob/master/android/src/main/java/com/mkuczera/vibrateFactory/VibrateWithDuration.java + +Expo Haptics is using `VibrationManager` API on SDK >= 31. See: https://github.com/expo/expo/blob/main/packages/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt#L19 +The timing and intensity of their haptic configurations though differs greatly from the original implementation. This +patch uses the new `VibrationManager` API to create the same vibration that would have been seen in the deprecated +`Vibration` API. diff --git a/patches/expo-haptics+12.8.1.patch b/patches/expo-haptics+12.8.1.patch new file mode 100644 index 0000000000..a95b56f3be --- /dev/null +++ b/patches/expo-haptics+12.8.1.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt +index 26c52af..b949a4c 100644 +--- a/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt ++++ b/node_modules/expo-haptics/android/src/main/java/expo/modules/haptics/HapticsModule.kt +@@ -42,7 +42,7 @@ class HapticsModule : Module() { + + private fun vibrate(type: HapticsVibrationType) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { +- vibrator.vibrate(VibrationEffect.createWaveform(type.timings, type.amplitudes, -1)) ++ vibrator.vibrate(VibrationEffect.createWaveform(type.oldSDKPattern, intArrayOf(0, 100), -1)) + } else { + @Suppress("DEPRECATION") + vibrator.vibrate(type.oldSDKPattern, -1) diff --git a/src/lib/haptics.ts b/src/lib/haptics.ts index b22d69d703..f59493d447 100644 --- a/src/lib/haptics.ts +++ b/src/lib/haptics.ts @@ -13,26 +13,30 @@ const hapticImpact: ImpactFeedbackStyle = isIOS : ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s export class Haptics { - static default() { - if (isWeb) { + static default(enabled: boolean) { + if (!enabled || isWeb) { return } + impactAsync(hapticImpact) } - static impact(type: ImpactFeedbackStyle = hapticImpact) { - if (isWeb) { + static impact(type: ImpactFeedbackStyle = hapticImpact, enabled: boolean) { + if (!enabled || isWeb) { return } impactAsync(type) } - static selection() { - if (isWeb) { + static selection(enabled: boolean) { + if (!enabled || isWeb) { return } selectionAsync() } - static notification = (type: 'success' | 'warning' | 'error') => { - if (isWeb) { + static notification = ( + type: 'success' | 'warning' | 'error', + enabled: boolean, + ) => { + if (!enabled || isWeb) { return } switch (type) { diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 4d8dbad86c..ec3ada6708 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -22,6 +22,7 @@ import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' import {useProfileShadow} from 'state/cache/profile-shadow' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {ProfileMenu} from '#/view/com/profile/ProfileMenu' import * as Toast from '#/view/com/util/Toast' import {atoms as a, tokens, useTheme} from '#/alf' @@ -64,6 +65,7 @@ let ProfileHeaderLabeler = ({ const {currentAccount, hasSession} = useSession() const {openModal} = useModalControls() const {track} = useAnalytics() + const isHapticsDisabled = useHapticsDisabled() const cantSubscribePrompt = Prompt.usePromptControl() const isSelf = currentAccount?.did === profile.did @@ -93,7 +95,7 @@ let ProfileHeaderLabeler = ({ return } try { - Haptics.default() + Haptics.default(isHapticsDisabled) if (likeUri) { await unlikeMod({uri: likeUri}) @@ -114,7 +116,7 @@ let ProfileHeaderLabeler = ({ ) logger.error(`Failed to toggle labeler like`, {message: e.message}) } - }, [labeler, likeUri, likeMod, unlikeMod, track, _]) + }, [labeler, isHapticsDisabled, likeUri, unlikeMod, track, likeMod, _]) const onPressEditProfile = React.useCallback(() => { track('ProfileHeader:EditProfileButtonClicked') diff --git a/src/state/persisted/legacy.ts b/src/state/persisted/legacy.ts index fd94a96a24..ca7967cd2e 100644 --- a/src/state/persisted/legacy.ts +++ b/src/state/persisted/legacy.ts @@ -2,7 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import {logger} from '#/logger' import {defaults, Schema, schema} from '#/state/persisted/schema' -import {write, read} from '#/state/persisted/store' +import {read, write} from '#/state/persisted/store' /** * The shape of the serialized data from our legacy Mobx store. @@ -113,6 +113,7 @@ export function transform(legacy: Partial): Schema { externalEmbeds: defaults.externalEmbeds, lastSelectedHomeFeed: defaults.lastSelectedHomeFeed, pdsAddressHistory: defaults.pdsAddressHistory, + disableHaptics: defaults.disableHaptics, } } diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 0aefaa4744..190cc80b03 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -1,4 +1,5 @@ import {z} from 'zod' + import {deviceLocales} from '#/platform/detection' const externalEmbedOptions = ['show', 'hide'] as const @@ -58,6 +59,7 @@ export const schema = z.object({ useInAppBrowser: z.boolean().optional(), lastSelectedHomeFeed: z.string().optional(), pdsAddressHistory: z.array(z.string()).optional(), + disableHaptics: z.boolean().optional(), }) export type Schema = z.infer @@ -93,4 +95,5 @@ export const defaults: Schema = { useInAppBrowser: undefined, lastSelectedHomeFeed: undefined, pdsAddressHistory: [], + disableHaptics: true, } diff --git a/src/state/preferences/disable-haptics.tsx b/src/state/preferences/disable-haptics.tsx new file mode 100644 index 0000000000..af2c55a182 --- /dev/null +++ b/src/state/preferences/disable-haptics.tsx @@ -0,0 +1,42 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = boolean +type SetContext = (v: boolean) => void + +const stateContext = React.createContext( + Boolean(persisted.defaults.disableHaptics), +) +const setContext = React.createContext((_: boolean) => {}) + +export function Provider({children}: {children: React.ReactNode}) { + const [state, setState] = React.useState( + Boolean(persisted.get('disableHaptics')), + ) + + const setStateWrapped = React.useCallback( + (hapticsEnabled: persisted.Schema['disableHaptics']) => { + setState(Boolean(hapticsEnabled)) + persisted.write('disableHaptics', hapticsEnabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate(() => { + setState(Boolean(persisted.get('disableHaptics'))) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export const useHapticsDisabled = () => React.useContext(stateContext) +export const useSetHapticsDisabled = () => React.useContext(setContext) diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index cf1d901511..804d0fc310 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -1,11 +1,12 @@ import React from 'react' -import {Provider as LanguagesProvider} from './languages' + import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required' import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts' +import {Provider as DisableHapticsProvider} from './disable-haptics' import {Provider as ExternalEmbedsProvider} from './external-embeds-prefs' import {Provider as InAppBrowserProvider} from './in-app-browser' +import {Provider as LanguagesProvider} from './languages' -export {useLanguagePrefs, useLanguagePrefsApi} from './languages' export { useRequireAltTextEnabled, useSetRequireAltTextEnabled, @@ -16,6 +17,7 @@ export { } from './external-embeds-prefs' export * from './hidden-posts' export {useLabelDefinitions} from './label-defs' +export {useLanguagePrefs, useLanguagePrefsApi} from './languages' export function Provider({children}: React.PropsWithChildren<{}>) { return ( @@ -23,7 +25,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 58874cd551..68ea1e22c5 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -32,6 +32,7 @@ import { } from '#/state/queries/post' import {useRequireAuth} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {useDialogControl} from '#/components/Dialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' import * as Prompt from '#/components/Prompt' @@ -67,6 +68,7 @@ let PostCtrls = ({ ) const requireAuth = useRequireAuth() const loggedOutWarningPromptControl = useDialogControl() + const isHapticsDisabled = useHapticsDisabled() const shouldShowLoggedOutWarning = React.useMemo(() => { return !!post.author.labels?.find( @@ -84,7 +86,7 @@ let PostCtrls = ({ const onPressToggleLike = React.useCallback(async () => { try { if (!post.viewer?.like) { - Haptics.default() + Haptics.default(isHapticsDisabled) await queueLike() } else { await queueUnlike() @@ -94,13 +96,13 @@ let PostCtrls = ({ throw e } } - }, [post.viewer?.like, queueLike, queueUnlike]) + }, [isHapticsDisabled, post.viewer?.like, queueLike, queueUnlike]) const onRepost = useCallback(async () => { closeModal() try { if (!post.viewer?.repost) { - Haptics.default() + Haptics.default(isHapticsDisabled) await queueRepost() } else { await queueUnrepost() @@ -110,7 +112,13 @@ let PostCtrls = ({ throw e } } - }, [post.viewer?.repost, queueRepost, queueUnrepost, closeModal]) + }, [ + closeModal, + post.viewer?.repost, + isHapticsDisabled, + queueRepost, + queueUnrepost, + ]) const onQuote = useCallback(() => { closeModal() @@ -123,15 +131,16 @@ let PostCtrls = ({ indexedAt: post.indexedAt, }, }) - Haptics.default() + Haptics.default(isHapticsDisabled) }, [ + closeModal, + openComposer, post.uri, post.cid, post.author, post.indexedAt, record.text, - openComposer, - closeModal, + isHapticsDisabled, ]) const onShare = useCallback(() => { diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 4560e14ebc..d50a64e6bb 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -39,6 +39,7 @@ import {pluralize} from 'lib/strings/helpers' import {makeRecordUri} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers' import {s} from 'lib/styles' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {Feed} from 'view/com/posts/Feed' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' @@ -159,6 +160,7 @@ export function ProfileFeedScreenInner({ const reportDialogControl = useReportDialogControl() const {openComposer} = useComposerControls() const {track} = useAnalytics() + const isHapticsDisabled = useHapticsDisabled() const feedSectionRef = React.useRef(null) const isScreenFocused = useIsFocused() @@ -201,7 +203,7 @@ export function ProfileFeedScreenInner({ const onToggleSaved = React.useCallback(async () => { try { - Haptics.default() + Haptics.default(isHapticsDisabled) if (isSaved) { await removeFeed({uri: feedInfo.uri}) @@ -221,18 +223,19 @@ export function ProfileFeedScreenInner({ logger.error('Failed up update feeds', {message: err}) } }, [ - feedInfo, + isHapticsDisabled, isSaved, - saveFeed, removeFeed, - resetSaveFeed, + feedInfo.uri, resetRemoveFeed, _, + saveFeed, + resetSaveFeed, ]) const onTogglePinned = React.useCallback(async () => { try { - Haptics.default() + Haptics.default(isHapticsDisabled) if (isPinned) { await unpinFeed({uri: feedInfo.uri}) @@ -245,7 +248,16 @@ export function ProfileFeedScreenInner({ Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed, _]) + }, [ + isHapticsDisabled, + isPinned, + unpinFeed, + feedInfo.uri, + resetUnpinFeed, + pinFeed, + resetPinFeed, + _, + ]) const onPressShare = React.useCallback(() => { const url = toShareUrl(feedInfo.route.href) @@ -517,6 +529,7 @@ function AboutSection({ const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri) const {hasSession} = useSession() const {track} = useAnalytics() + const isHapticsDisabled = useHapticsDisabled() const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation() const {mutateAsync: unlikeFeed, isPending: isUnlikePending} = useUnlikeMutation() @@ -527,7 +540,7 @@ function AboutSection({ const onToggleLiked = React.useCallback(async () => { try { - Haptics.default() + Haptics.default(isHapticsDisabled) if (isLiked && likeUri) { await unlikeFeed({uri: likeUri}) @@ -546,7 +559,17 @@ function AboutSection({ ) logger.error('Failed up toggle like', {message: err}) } - }, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _]) + }, [ + isHapticsDisabled, + isLiked, + likeUri, + unlikeFeed, + track, + likeFeed, + feedInfo.uri, + feedInfo.cid, + _, + ]) return ( diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index 58b89f2399..c361acdbdd 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -1,69 +1,71 @@ import React, {useCallback, useMemo} from 'react' import {Pressable, StyleSheet, View} from 'react-native' +import {AppBskyGraphDefs, AtUri, RichText as RichTextAPI} from '@atproto/api' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useFocusEffect, useIsFocused} from '@react-navigation/native' -import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {useNavigation} from '@react-navigation/native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {AppBskyGraphDefs, AtUri, RichText as RichTextAPI} from '@atproto/api' import {useQueryClient} from '@tanstack/react-query' -import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' -import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' -import {Feed} from 'view/com/posts/Feed' -import {Text} from 'view/com/util/text/Text' -import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown' -import {CenteredView} from 'view/com/util/Views' -import {EmptyState} from 'view/com/util/EmptyState' -import {LoadingScreen} from 'view/com/util/LoadingScreen' -import {RichText} from '#/components/RichText' -import {Button} from 'view/com/util/forms/Button' -import {TextLink} from 'view/com/util/Link' -import {ListRef} from 'view/com/util/List' -import * as Toast from 'view/com/util/Toast' -import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' -import {FAB} from 'view/com/util/fab/FAB' -import {Haptics} from 'lib/haptics' -import {FeedDescriptor} from '#/state/queries/post-feed' -import {usePalette} from 'lib/hooks/usePalette' -import {useSetTitle} from 'lib/hooks/useSetTitle' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' -import {NavigationProp} from 'lib/routes/types' -import {toShareUrl} from 'lib/strings/url-helpers' -import {shareUrl} from 'lib/sharing' -import {s} from 'lib/styles' -import {sanitizeHandle} from 'lib/strings/handles' -import {makeProfileLink, makeListLink} from 'lib/routes/links' -import {ComposeIcon2} from 'lib/icons' -import {ListMembers} from '#/view/com/lists/ListMembers' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useSetMinimalShellMode} from '#/state/shell' + +import {useAnalytics} from '#/lib/analytics/analytics' +import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {isNative, isWeb} from '#/platform/detection' +import {listenSoftReset} from '#/state/events' import {useModalControls} from '#/state/modals' -import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' -import {useResolveUriQuery} from '#/state/queries/resolve-uri' import { - useListQuery, - useListMuteMutation, useListBlockMutation, useListDeleteMutation, + useListMuteMutation, + useListQuery, } from '#/state/queries/list' -import {cleanError} from '#/lib/strings/errors' -import {useSession} from '#/state/session' -import {useComposerControls} from '#/state/shell/composer' -import {isNative, isWeb} from '#/platform/detection' -import {truncateAndInvalidate} from '#/state/queries/util' +import {FeedDescriptor} from '#/state/queries/post-feed' +import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import { - usePreferencesQuery, usePinFeedMutation, - useUnpinFeedMutation, + usePreferencesQuery, useSetSaveFeedsMutation, + useUnpinFeedMutation, } from '#/state/queries/preferences' -import {logger} from '#/logger' -import {useAnalytics} from '#/lib/analytics/analytics' -import {listenSoftReset} from '#/state/events' +import {useResolveUriQuery} from '#/state/queries/resolve-uri' +import {truncateAndInvalidate} from '#/state/queries/util' +import {useSession} from '#/state/session' +import {useSetMinimalShellMode} from '#/state/shell' +import {useComposerControls} from '#/state/shell/composer' +import {Haptics} from 'lib/haptics' +import {usePalette} from 'lib/hooks/usePalette' +import {useSetTitle} from 'lib/hooks/useSetTitle' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {ComposeIcon2} from 'lib/icons' +import {makeListLink, makeProfileLink} from 'lib/routes/links' +import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' +import {NavigationProp} from 'lib/routes/types' +import {shareUrl} from 'lib/sharing' +import {sanitizeHandle} from 'lib/strings/handles' +import {toShareUrl} from 'lib/strings/url-helpers' +import {s} from 'lib/styles' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' +import {ListMembers} from '#/view/com/lists/ListMembers' +import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' +import {Feed} from 'view/com/posts/Feed' +import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' +import {EmptyState} from 'view/com/util/EmptyState' +import {FAB} from 'view/com/util/fab/FAB' +import {Button} from 'view/com/util/forms/Button' +import {DropdownItem, NativeDropdown} from 'view/com/util/forms/NativeDropdown' +import {TextLink} from 'view/com/util/Link' +import {ListRef} from 'view/com/util/List' +import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' +import {LoadingScreen} from 'view/com/util/LoadingScreen' +import {Text} from 'view/com/util/text/Text' +import * as Toast from 'view/com/util/Toast' +import {CenteredView} from 'view/com/util/Views' import {atoms as a, useTheme} from '#/alf' -import * as Prompt from '#/components/Prompt' import {useDialogControl} from '#/components/Dialog' +import * as Prompt from '#/components/Prompt' +import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog' +import {RichText} from '#/components/RichText' const SECTION_TITLES_CURATE = ['Posts', 'About'] const SECTION_TITLES_MOD = ['About'] @@ -254,6 +256,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const {data: preferences} = usePreferencesQuery() const {mutate: setSavedFeeds} = useSetSaveFeedsMutation() const {track} = useAnalytics() + const isHapticsDisabled = useHapticsDisabled() const deleteListPromptControl = useDialogControl() const subscribeMutePromptControl = useDialogControl() @@ -263,7 +266,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const isSaved = preferences?.feeds?.saved?.includes(list.uri) const onTogglePinned = React.useCallback(async () => { - Haptics.default() + Haptics.default(isHapticsDisabled) try { if (isPinned) { @@ -275,7 +278,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [list.uri, isPinned, pinFeed, unpinFeed, _]) + }, [isHapticsDisabled, isPinned, unpinFeed, list.uri, pinFeed, _]) const onSubscribeMute = useCallback(async () => { try { diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 251c706384..54e73ddede 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -1,31 +1,33 @@ import React from 'react' -import {StyleSheet, View, ActivityIndicator, Pressable} from 'react-native' +import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' + import {track} from '#/lib/analytics/analytics' -import {useAnalytics} from 'lib/analytics/analytics' -import {usePalette} from 'lib/hooks/usePalette' -import {CommonNavigatorParams} from 'lib/routes/types' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {ViewHeader} from 'view/com/util/ViewHeader' -import {ScrollView, CenteredView} from 'view/com/util/Views' -import {Text} from 'view/com/util/text/Text' -import {s, colors} from 'lib/styles' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import * as Toast from 'view/com/util/Toast' -import {Haptics} from 'lib/haptics' -import {TextLink} from 'view/com/util/Link' import {logger} from '#/logger' -import {useSetMinimalShellMode} from '#/state/shell' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' import { - usePreferencesQuery, usePinFeedMutation, - useUnpinFeedMutation, + usePreferencesQuery, useSetSaveFeedsMutation, + useUnpinFeedMutation, } from '#/state/queries/preferences' +import {useSetMinimalShellMode} from '#/state/shell' +import {useAnalytics} from 'lib/analytics/analytics' +import {Haptics} from 'lib/haptics' +import {usePalette} from 'lib/hooks/usePalette' +import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {CommonNavigatorParams} from 'lib/routes/types' +import {colors, s} from 'lib/styles' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' +import {TextLink} from 'view/com/util/Link' +import {Text} from 'view/com/util/text/Text' +import * as Toast from 'view/com/util/Toast' +import {ViewHeader} from 'view/com/util/ViewHeader' +import {CenteredView, ScrollView} from 'view/com/util/Views' const HITSLOP_TOP = { top: 20, @@ -189,13 +191,14 @@ function ListItem({ }) { const pal = usePalette('default') const {_} = useLingui() + const isHapticsDisabled = useHapticsDisabled() const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() const {isPending: isUnpinPending, mutateAsync: unpinFeed} = useUnpinFeedMutation() const isPending = isPinPending || isUnpinPending const onTogglePinned = React.useCallback(async () => { - Haptics.default() + Haptics.default(isHapticsDisabled) try { resetSaveFeedsMutationState() @@ -209,7 +212,15 @@ function ListItem({ Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState, _]) + }, [ + isHapticsDisabled, + resetSaveFeedsMutationState, + isPinned, + unpinFeed, + feedUri, + pinFeed, + _, + ]) const onPressUp = React.useCallback(async () => { if (!isPinned) return diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx index 830a73ff26..8a7fa5e714 100644 --- a/src/view/screens/Settings/index.tsx +++ b/src/view/screens/Settings/index.tsx @@ -20,10 +20,9 @@ import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' -import {isNative} from '#/platform/detection' +import {isIOS, isNative} from '#/platform/detection' import {useModalControls} from '#/state/modals' import {clearLegacyStorage} from '#/state/persisted/legacy' -// TODO import {useInviteCodesQuery} from '#/state/queries/invites' import {clear as clearStorage} from '#/state/persisted/store' import { useRequireAltTextEnabled, @@ -57,6 +56,10 @@ import {makeProfileLink} from 'lib/routes/links' import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types' import {colors, s} from 'lib/styles' +import { + useHapticsDisabled, + useSetHapticsDisabled, +} from 'state/preferences/disable-haptics' import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn' import {SelectableBtn} from 'view/com/util/forms/SelectableBtn' import {ToggleButton} from 'view/com/util/forms/ToggleButton' @@ -155,6 +158,8 @@ export function SettingsScreen({}: Props) { const setRequireAltTextEnabled = useSetRequireAltTextEnabled() const inAppBrowserPref = useInAppBrowser() const setUseInAppBrowser = useSetInAppBrowser() + const isHapticsDisabled = useHapticsDisabled() + const setHapticsDisabled = useSetHapticsDisabled() const onboardingDispatch = useOnboardingDispatch() const navigation = useNavigation() const {isMobile} = useWebMediaQueries() @@ -162,9 +167,6 @@ export function SettingsScreen({}: Props) { const {openModal} = useModalControls() const {isSwitchingAccounts, accounts, currentAccount} = useSession() const {mutate: clearPreferences} = useClearPreferencesMutation() - // TODO - // const {data: invites} = useInviteCodesQuery() - // const invitesAvailable = invites?.available?.length ?? 0 const {setShowLoggedOut} = useLoggedOutViewControls() const closeAllActiveElements = useCloseAllActiveElements() const exportCarControl = useDialogControl() @@ -220,13 +222,6 @@ export function SettingsScreen({}: Props) { exportCarControl.open() }, [exportCarControl]) - /* TODO - const onPressInviteCodes = React.useCallback(() => { - track('Settings:InvitecodesButtonClicked') - openModal({name: 'invite-codes'}) - }, [track, openModal]) - */ - const onPressLanguageSettings = React.useCallback(() => { navigation.navigate('LanguageSettings') }, [navigation]) @@ -414,58 +409,6 @@ export function SettingsScreen({}: Props) { - {/* TODO ( - <> - - Invite a Friend - - - - 0 ? primaryBg : pal.btn, - ]}> - 0 - ? primaryText - : pal.text) as FontAwesomeIconStyle - } - /> - - 0 ? pal.link : pal.text}> - {invites?.disabled ? ( - - Your invite codes are hidden when logged in using an App - Password - - ) : invitesAvailable === 1 ? ( - {invitesAvailable} invite code available - ) : ( - {invitesAvailable} invite codes available - )} - - - - - - )*/} - Accessibility @@ -738,6 +681,19 @@ export function SettingsScreen({}: Props) { /> )} + {isNative && ( + + setHapticsDisabled(!isHapticsDisabled)} + /> + + )} Account diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index f41631a969..1e889f7721 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -32,6 +32,7 @@ import {useSession} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useShellLayout} from '#/state/shell/shell-layout' import {useCloseAllActiveElements} from '#/state/util' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {Button} from '#/view/com/util/forms/Button' import {Text} from '#/view/com/util/text/Text' import {UserAvatar} from '#/view/com/util/UserAvatar' @@ -59,6 +60,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { const closeAllActiveElements = useCloseAllActiveElements() const dedupe = useDedupe() const accountSwitchControl = useDialogControl() + const isHapticsDisabled = useHapticsDisabled() const showSignIn = React.useCallback(() => { closeAllActiveElements() @@ -104,9 +106,9 @@ export function BottomBar({navigation}: BottomTabBarProps) { }, [onPressTab]) const onLongPressProfile = React.useCallback(() => { - Haptics.default() + Haptics.default(isHapticsDisabled) accountSwitchControl.open() - }, [accountSwitchControl]) + }, [accountSwitchControl, isHapticsDisabled]) return ( <> From 7918574eb6fe3357edcba33fbf0df78790750237 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 11:38:08 -0700 Subject: [PATCH 2/7] update haptics.ts --- src/lib/haptics.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/haptics.ts b/src/lib/haptics.ts index f59493d447..6525803e41 100644 --- a/src/lib/haptics.ts +++ b/src/lib/haptics.ts @@ -13,21 +13,21 @@ const hapticImpact: ImpactFeedbackStyle = isIOS : ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s export class Haptics { - static default(enabled: boolean) { - if (!enabled || isWeb) { + static default(disabled: boolean) { + if (disabled || isWeb) { return } impactAsync(hapticImpact) } - static impact(type: ImpactFeedbackStyle = hapticImpact, enabled: boolean) { - if (!enabled || isWeb) { + static impact(type: ImpactFeedbackStyle = hapticImpact, disabled: boolean) { + if (disabled || isWeb) { return } impactAsync(type) } - static selection(enabled: boolean) { - if (!enabled || isWeb) { + static selection(disabled: boolean) { + if (disabled || isWeb) { return } selectionAsync() From 77f70a120d04ea25c6cef9535faf1006a37f5c26 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 11:46:20 -0700 Subject: [PATCH 3/7] default to false --- src/state/persisted/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 190cc80b03..67e082a95d 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -95,5 +95,5 @@ export const defaults: Schema = { useInAppBrowser: undefined, lastSelectedHomeFeed: undefined, pdsAddressHistory: [], - disableHaptics: true, + disableHaptics: false, } From 38ebcdbe969b56254a923fe80323e1b23d8c3092 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 14:54:13 -0700 Subject: [PATCH 4/7] simplify to `playHaptic` --- src/lib/haptics.ts | 47 ++----------------- .../Profile/Header/ProfileHeaderLabeler.tsx | 4 +- src/view/com/util/post-ctrls/PostCtrls.tsx | 8 ++-- src/view/screens/ProfileFeed.tsx | 8 ++-- src/view/screens/ProfileList.tsx | 4 +- src/view/screens/SavedFeeds.tsx | 4 +- src/view/shell/bottom-bar/BottomBar.tsx | 4 +- 7 files changed, 21 insertions(+), 58 deletions(-) diff --git a/src/lib/haptics.ts b/src/lib/haptics.ts index 6525803e41..3971c7e085 100644 --- a/src/lib/haptics.ts +++ b/src/lib/haptics.ts @@ -1,10 +1,4 @@ -import { - impactAsync, - ImpactFeedbackStyle, - notificationAsync, - NotificationFeedbackType, - selectionAsync, -} from 'expo-haptics' +import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' import {isIOS, isWeb} from 'platform/detection' @@ -12,40 +6,9 @@ const hapticImpact: ImpactFeedbackStyle = isIOS ? ImpactFeedbackStyle.Medium : ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s -export class Haptics { - static default(disabled: boolean) { - if (disabled || isWeb) { - return - } - - impactAsync(hapticImpact) - } - static impact(type: ImpactFeedbackStyle = hapticImpact, disabled: boolean) { - if (disabled || isWeb) { - return - } - impactAsync(type) - } - static selection(disabled: boolean) { - if (disabled || isWeb) { - return - } - selectionAsync() - } - static notification = ( - type: 'success' | 'warning' | 'error', - enabled: boolean, - ) => { - if (!enabled || isWeb) { - return - } - switch (type) { - case 'success': - return notificationAsync(NotificationFeedbackType.Success) - case 'warning': - return notificationAsync(NotificationFeedbackType.Warning) - case 'error': - return notificationAsync(NotificationFeedbackType.Error) - } +export function playHaptic(disabled: boolean) { + if (disabled || isWeb) { + return } + impactAsync(hapticImpact) } diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index ec3ada6708..8878504990 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -10,7 +10,6 @@ import { import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Haptics} from '#/lib/haptics' import {isAppLabeler} from '#/lib/moderation' import {pluralize} from '#/lib/strings/helpers' import {logger} from '#/logger' @@ -21,6 +20,7 @@ import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' +import {playHaptic} from 'lib/haptics' import {useProfileShadow} from 'state/cache/profile-shadow' import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {ProfileMenu} from '#/view/com/profile/ProfileMenu' @@ -95,7 +95,7 @@ let ProfileHeaderLabeler = ({ return } try { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) if (likeUri) { await unlikeMod({uri: likeUri}) diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index 68ea1e22c5..cb49f36615 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -16,7 +16,7 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' -import {Haptics} from '#/lib/haptics' +import {playHaptic} from '#/lib/haptics' import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' @@ -86,7 +86,7 @@ let PostCtrls = ({ const onPressToggleLike = React.useCallback(async () => { try { if (!post.viewer?.like) { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) await queueLike() } else { await queueUnlike() @@ -102,7 +102,7 @@ let PostCtrls = ({ closeModal() try { if (!post.viewer?.repost) { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) await queueRepost() } else { await queueUnrepost() @@ -131,7 +131,7 @@ let PostCtrls = ({ indexedAt: post.indexedAt, }, }) - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) }, [ closeModal, openComposer, diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index d50a64e6bb..ad6ebd81f8 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -27,7 +27,7 @@ import {truncateAndInvalidate} from '#/state/queries/util' import {useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {useAnalytics} from 'lib/analytics/analytics' -import {Haptics} from 'lib/haptics' +import {playHaptic} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {ComposeIcon2} from 'lib/icons' @@ -203,7 +203,7 @@ export function ProfileFeedScreenInner({ const onToggleSaved = React.useCallback(async () => { try { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) if (isSaved) { await removeFeed({uri: feedInfo.uri}) @@ -235,7 +235,7 @@ export function ProfileFeedScreenInner({ const onTogglePinned = React.useCallback(async () => { try { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) if (isPinned) { await unpinFeed({uri: feedInfo.uri}) @@ -540,7 +540,7 @@ function AboutSection({ const onToggleLiked = React.useCallback(async () => { try { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) if (isLiked && likeUri) { await unlikeFeed({uri: likeUri}) diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index c361acdbdd..f6c10c06f2 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -33,7 +33,7 @@ import {truncateAndInvalidate} from '#/state/queries/util' import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' -import {Haptics} from 'lib/haptics' +import {playHaptic} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' @@ -266,7 +266,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const isSaved = preferences?.feeds?.saved?.includes(list.uri) const onTogglePinned = React.useCallback(async () => { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) try { if (isPinned) { diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 54e73ddede..872a07428a 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -16,7 +16,7 @@ import { } from '#/state/queries/preferences' import {useSetMinimalShellMode} from '#/state/shell' import {useAnalytics} from 'lib/analytics/analytics' -import {Haptics} from 'lib/haptics' +import {playHaptic} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {CommonNavigatorParams} from 'lib/routes/types' @@ -198,7 +198,7 @@ function ListItem({ const isPending = isPinPending || isUnpinPending const onTogglePinned = React.useCallback(async () => { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) try { resetSaveFeedsMutationState() diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index 1e889f7721..072357d71f 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -8,7 +8,7 @@ import {BottomTabBarProps} from '@react-navigation/bottom-tabs' import {StackActions} from '@react-navigation/native' import {useAnalytics} from '#/lib/analytics/analytics' -import {Haptics} from '#/lib/haptics' +import {playHaptic} from '#/lib/haptics' import {useDedupe} from '#/lib/hooks/useDedupe' import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode' import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' @@ -106,7 +106,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { }, [onPressTab]) const onLongPressProfile = React.useCallback(() => { - Haptics.default(isHapticsDisabled) + playHaptic(isHapticsDisabled) accountSwitchControl.open() }, [accountSwitchControl, isHapticsDisabled]) From 85191f8fef7343737498a7570ef9d548aa3c87a0 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 15:04:51 -0700 Subject: [PATCH 5/7] just leave them as `feedInfo` --- src/view/screens/ProfileFeed.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index ad6ebd81f8..2ab2e631dc 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -226,7 +226,7 @@ export function ProfileFeedScreenInner({ isHapticsDisabled, isSaved, removeFeed, - feedInfo.uri, + feedInfo, resetRemoveFeed, _, saveFeed, @@ -252,7 +252,7 @@ export function ProfileFeedScreenInner({ isHapticsDisabled, isPinned, unpinFeed, - feedInfo.uri, + feedInfo, resetUnpinFeed, pinFeed, resetPinFeed, @@ -566,8 +566,7 @@ function AboutSection({ unlikeFeed, track, likeFeed, - feedInfo.uri, - feedInfo.cid, + feedInfo, _, ]) From 461fa81771183df85ffb4f7674464709db769c64 Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 15:14:35 -0700 Subject: [PATCH 6/7] use a hook for `playHaptic` --- src/lib/haptics.ts | 16 +++++++++---- src/view/com/util/post-ctrls/PostCtrls.tsx | 23 +++++++----------- src/view/screens/ProfileFeed.tsx | 28 +++++++--------------- src/view/screens/ProfileList.tsx | 9 ++++--- src/view/screens/SavedFeeds.tsx | 9 ++++--- src/view/shell/bottom-bar/BottomBar.tsx | 9 ++++--- 6 files changed, 40 insertions(+), 54 deletions(-) diff --git a/src/lib/haptics.ts b/src/lib/haptics.ts index 3971c7e085..02940f793d 100644 --- a/src/lib/haptics.ts +++ b/src/lib/haptics.ts @@ -1,14 +1,20 @@ +import React from 'react' import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics' import {isIOS, isWeb} from 'platform/detection' +import {useHapticsDisabled} from 'state/preferences/disable-haptics' const hapticImpact: ImpactFeedbackStyle = isIOS ? ImpactFeedbackStyle.Medium : ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s -export function playHaptic(disabled: boolean) { - if (disabled || isWeb) { - return - } - impactAsync(hapticImpact) +export function useHaptics() { + const isHapticsDisabled = useHapticsDisabled() + + return React.useCallback(() => { + if (isHapticsDisabled || isWeb) { + return + } + impactAsync(hapticImpact) + }, [isHapticsDisabled]) } diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index cb49f36615..cd4a363730 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -16,7 +16,6 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {HITSLOP_10, HITSLOP_20} from '#/lib/constants' -import {playHaptic} from '#/lib/haptics' import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons' import {makeProfileLink} from '#/lib/routes/links' import {shareUrl} from '#/lib/sharing' @@ -32,7 +31,7 @@ import { } from '#/state/queries/post' import {useRequireAuth} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' +import {useHaptics} from 'lib/haptics' import {useDialogControl} from '#/components/Dialog' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox' import * as Prompt from '#/components/Prompt' @@ -68,7 +67,7 @@ let PostCtrls = ({ ) const requireAuth = useRequireAuth() const loggedOutWarningPromptControl = useDialogControl() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const shouldShowLoggedOutWarning = React.useMemo(() => { return !!post.author.labels?.find( @@ -86,7 +85,7 @@ let PostCtrls = ({ const onPressToggleLike = React.useCallback(async () => { try { if (!post.viewer?.like) { - playHaptic(isHapticsDisabled) + playHaptic() await queueLike() } else { await queueUnlike() @@ -96,13 +95,13 @@ let PostCtrls = ({ throw e } } - }, [isHapticsDisabled, post.viewer?.like, queueLike, queueUnlike]) + }, [playHaptic, post.viewer?.like, queueLike, queueUnlike]) const onRepost = useCallback(async () => { closeModal() try { if (!post.viewer?.repost) { - playHaptic(isHapticsDisabled) + playHaptic() await queueRepost() } else { await queueUnrepost() @@ -112,13 +111,7 @@ let PostCtrls = ({ throw e } } - }, [ - closeModal, - post.viewer?.repost, - isHapticsDisabled, - queueRepost, - queueUnrepost, - ]) + }, [closeModal, post.viewer?.repost, playHaptic, queueRepost, queueUnrepost]) const onQuote = useCallback(() => { closeModal() @@ -131,7 +124,7 @@ let PostCtrls = ({ indexedAt: post.indexedAt, }, }) - playHaptic(isHapticsDisabled) + playHaptic() }, [ closeModal, openComposer, @@ -140,7 +133,7 @@ let PostCtrls = ({ post.author, post.indexedAt, record.text, - isHapticsDisabled, + playHaptic, ]) const onShare = useCallback(() => { diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 2ab2e631dc..814c1e8558 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -27,7 +27,7 @@ import {truncateAndInvalidate} from '#/state/queries/util' import {useSession} from '#/state/session' import {useComposerControls} from '#/state/shell/composer' import {useAnalytics} from 'lib/analytics/analytics' -import {playHaptic} from 'lib/haptics' +import {useHaptics} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {ComposeIcon2} from 'lib/icons' @@ -39,7 +39,6 @@ import {pluralize} from 'lib/strings/helpers' import {makeRecordUri} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers' import {s} from 'lib/styles' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {Feed} from 'view/com/posts/Feed' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' @@ -160,7 +159,7 @@ export function ProfileFeedScreenInner({ const reportDialogControl = useReportDialogControl() const {openComposer} = useComposerControls() const {track} = useAnalytics() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const feedSectionRef = React.useRef(null) const isScreenFocused = useIsFocused() @@ -203,7 +202,7 @@ export function ProfileFeedScreenInner({ const onToggleSaved = React.useCallback(async () => { try { - playHaptic(isHapticsDisabled) + playHaptic() if (isSaved) { await removeFeed({uri: feedInfo.uri}) @@ -223,7 +222,7 @@ export function ProfileFeedScreenInner({ logger.error('Failed up update feeds', {message: err}) } }, [ - isHapticsDisabled, + playHaptic, isSaved, removeFeed, feedInfo, @@ -235,7 +234,7 @@ export function ProfileFeedScreenInner({ const onTogglePinned = React.useCallback(async () => { try { - playHaptic(isHapticsDisabled) + playHaptic() if (isPinned) { await unpinFeed({uri: feedInfo.uri}) @@ -249,7 +248,7 @@ export function ProfileFeedScreenInner({ logger.error('Failed to toggle pinned feed', {message: e}) } }, [ - isHapticsDisabled, + playHaptic, isPinned, unpinFeed, feedInfo, @@ -529,7 +528,7 @@ function AboutSection({ const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri) const {hasSession} = useSession() const {track} = useAnalytics() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation() const {mutateAsync: unlikeFeed, isPending: isUnlikePending} = useUnlikeMutation() @@ -540,7 +539,7 @@ function AboutSection({ const onToggleLiked = React.useCallback(async () => { try { - playHaptic(isHapticsDisabled) + playHaptic() if (isLiked && likeUri) { await unlikeFeed({uri: likeUri}) @@ -559,16 +558,7 @@ function AboutSection({ ) logger.error('Failed up toggle like', {message: err}) } - }, [ - isHapticsDisabled, - isLiked, - likeUri, - unlikeFeed, - track, - likeFeed, - feedInfo, - _, - ]) + }, [playHaptic, isLiked, likeUri, unlikeFeed, track, likeFeed, feedInfo, _]) return ( diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index f6c10c06f2..1d93a9fd7d 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -33,7 +33,7 @@ import {truncateAndInvalidate} from '#/state/queries/util' import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' -import {playHaptic} from 'lib/haptics' +import {useHaptics} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useSetTitle} from 'lib/hooks/useSetTitle' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' @@ -45,7 +45,6 @@ import {shareUrl} from 'lib/sharing' import {sanitizeHandle} from 'lib/strings/handles' import {toShareUrl} from 'lib/strings/url-helpers' import {s} from 'lib/styles' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {ListMembers} from '#/view/com/lists/ListMembers' import {PagerWithHeader} from 'view/com/pager/PagerWithHeader' import {Feed} from 'view/com/posts/Feed' @@ -256,7 +255,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const {data: preferences} = usePreferencesQuery() const {mutate: setSavedFeeds} = useSetSaveFeedsMutation() const {track} = useAnalytics() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const deleteListPromptControl = useDialogControl() const subscribeMutePromptControl = useDialogControl() @@ -266,7 +265,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { const isSaved = preferences?.feeds?.saved?.includes(list.uri) const onTogglePinned = React.useCallback(async () => { - playHaptic(isHapticsDisabled) + playHaptic() try { if (isPinned) { @@ -278,7 +277,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) { Toast.show(_(msg`There was an issue contacting the server`)) logger.error('Failed to toggle pinned feed', {message: e}) } - }, [isHapticsDisabled, isPinned, unpinFeed, list.uri, pinFeed, _]) + }, [playHaptic, isPinned, unpinFeed, list.uri, pinFeed, _]) const onSubscribeMute = useCallback(async () => { try { diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 872a07428a..0003dbd5d9 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -16,12 +16,11 @@ import { } from '#/state/queries/preferences' import {useSetMinimalShellMode} from '#/state/shell' import {useAnalytics} from 'lib/analytics/analytics' -import {playHaptic} from 'lib/haptics' +import {useHaptics} from 'lib/haptics' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {CommonNavigatorParams} from 'lib/routes/types' import {colors, s} from 'lib/styles' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {TextLink} from 'view/com/util/Link' import {Text} from 'view/com/util/text/Text' @@ -191,14 +190,14 @@ function ListItem({ }) { const pal = usePalette('default') const {_} = useLingui() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() const {isPending: isUnpinPending, mutateAsync: unpinFeed} = useUnpinFeedMutation() const isPending = isPinPending || isUnpinPending const onTogglePinned = React.useCallback(async () => { - playHaptic(isHapticsDisabled) + playHaptic() try { resetSaveFeedsMutationState() @@ -213,7 +212,7 @@ function ListItem({ logger.error('Failed to toggle pinned feed', {message: e}) } }, [ - isHapticsDisabled, + playHaptic, resetSaveFeedsMutationState, isPinned, unpinFeed, diff --git a/src/view/shell/bottom-bar/BottomBar.tsx b/src/view/shell/bottom-bar/BottomBar.tsx index 072357d71f..c35fa106d2 100644 --- a/src/view/shell/bottom-bar/BottomBar.tsx +++ b/src/view/shell/bottom-bar/BottomBar.tsx @@ -8,7 +8,7 @@ import {BottomTabBarProps} from '@react-navigation/bottom-tabs' import {StackActions} from '@react-navigation/native' import {useAnalytics} from '#/lib/analytics/analytics' -import {playHaptic} from '#/lib/haptics' +import {useHaptics} from '#/lib/haptics' import {useDedupe} from '#/lib/hooks/useDedupe' import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode' import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState' @@ -32,7 +32,6 @@ import {useSession} from '#/state/session' import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useShellLayout} from '#/state/shell/shell-layout' import {useCloseAllActiveElements} from '#/state/util' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {Button} from '#/view/com/util/forms/Button' import {Text} from '#/view/com/util/text/Text' import {UserAvatar} from '#/view/com/util/UserAvatar' @@ -60,7 +59,7 @@ export function BottomBar({navigation}: BottomTabBarProps) { const closeAllActiveElements = useCloseAllActiveElements() const dedupe = useDedupe() const accountSwitchControl = useDialogControl() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const showSignIn = React.useCallback(() => { closeAllActiveElements() @@ -106,9 +105,9 @@ export function BottomBar({navigation}: BottomTabBarProps) { }, [onPressTab]) const onLongPressProfile = React.useCallback(() => { - playHaptic(isHapticsDisabled) + playHaptic() accountSwitchControl.open() - }, [accountSwitchControl, isHapticsDisabled]) + }, [accountSwitchControl, playHaptic]) return ( <> From cde0ecfb245642cb614ab0b76d40bb0df3fa50bc Mon Sep 17 00:00:00 2001 From: Hailey Date: Thu, 11 Apr 2024 15:15:08 -0700 Subject: [PATCH 7/7] missed one of them --- src/screens/Profile/Header/ProfileHeaderLabeler.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index 8878504990..d0fd5e20bd 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -20,9 +20,8 @@ import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like' import {usePreferencesQuery} from '#/state/queries/preferences' import {useSession} from '#/state/session' import {useAnalytics} from 'lib/analytics/analytics' -import {playHaptic} from 'lib/haptics' +import {useHaptics} from 'lib/haptics' import {useProfileShadow} from 'state/cache/profile-shadow' -import {useHapticsDisabled} from 'state/preferences/disable-haptics' import {ProfileMenu} from '#/view/com/profile/ProfileMenu' import * as Toast from '#/view/com/util/Toast' import {atoms as a, tokens, useTheme} from '#/alf' @@ -65,7 +64,7 @@ let ProfileHeaderLabeler = ({ const {currentAccount, hasSession} = useSession() const {openModal} = useModalControls() const {track} = useAnalytics() - const isHapticsDisabled = useHapticsDisabled() + const playHaptic = useHaptics() const cantSubscribePrompt = Prompt.usePromptControl() const isSelf = currentAccount?.did === profile.did @@ -95,7 +94,7 @@ let ProfileHeaderLabeler = ({ return } try { - playHaptic(isHapticsDisabled) + playHaptic() if (likeUri) { await unlikeMod({uri: likeUri}) @@ -116,7 +115,7 @@ let ProfileHeaderLabeler = ({ ) logger.error(`Failed to toggle labeler like`, {message: e.message}) } - }, [labeler, isHapticsDisabled, likeUri, unlikeMod, track, likeMod, _]) + }, [labeler, playHaptic, likeUri, unlikeMod, track, likeMod, _]) const onPressEditProfile = React.useCallback(() => { track('ProfileHeader:EditProfileButtonClicked')