Improve Android haptic, offer toggle for haptics in the app (#3482)
* improve android haptics, offer toggle for haptics * update haptics.ts * default to false * simplify to `playHaptic` * just leave them as `feedInfo` * use a hook for `playHaptic` * missed one of themzio/stable
parent
9007810cdb
commit
740cd029d7
|
@ -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.
|
|
@ -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)
|
|
@ -1,47 +1,20 @@
|
||||||
import {
|
import React from 'react'
|
||||||
impactAsync,
|
import {impactAsync, ImpactFeedbackStyle} from 'expo-haptics'
|
||||||
ImpactFeedbackStyle,
|
|
||||||
notificationAsync,
|
|
||||||
NotificationFeedbackType,
|
|
||||||
selectionAsync,
|
|
||||||
} from 'expo-haptics'
|
|
||||||
|
|
||||||
import {isIOS, isWeb} from 'platform/detection'
|
import {isIOS, isWeb} from 'platform/detection'
|
||||||
|
import {useHapticsDisabled} from 'state/preferences/disable-haptics'
|
||||||
|
|
||||||
const hapticImpact: ImpactFeedbackStyle = isIOS
|
const hapticImpact: ImpactFeedbackStyle = isIOS
|
||||||
? ImpactFeedbackStyle.Medium
|
? ImpactFeedbackStyle.Medium
|
||||||
: ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s
|
: ImpactFeedbackStyle.Light // Users said the medium impact was too strong on Android; see APP-537s
|
||||||
|
|
||||||
export class Haptics {
|
export function useHaptics() {
|
||||||
static default() {
|
const isHapticsDisabled = useHapticsDisabled()
|
||||||
if (isWeb) {
|
|
||||||
|
return React.useCallback(() => {
|
||||||
|
if (isHapticsDisabled || isWeb) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
impactAsync(hapticImpact)
|
impactAsync(hapticImpact)
|
||||||
}
|
}, [isHapticsDisabled])
|
||||||
static impact(type: ImpactFeedbackStyle = hapticImpact) {
|
|
||||||
if (isWeb) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
impactAsync(type)
|
|
||||||
}
|
|
||||||
static selection() {
|
|
||||||
if (isWeb) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
selectionAsync()
|
|
||||||
}
|
|
||||||
static notification = (type: 'success' | 'warning' | 'error') => {
|
|
||||||
if (isWeb) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case 'success':
|
|
||||||
return notificationAsync(NotificationFeedbackType.Success)
|
|
||||||
case 'warning':
|
|
||||||
return notificationAsync(NotificationFeedbackType.Warning)
|
|
||||||
case 'error':
|
|
||||||
return notificationAsync(NotificationFeedbackType.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {Haptics} from '#/lib/haptics'
|
|
||||||
import {isAppLabeler} from '#/lib/moderation'
|
import {isAppLabeler} from '#/lib/moderation'
|
||||||
import {pluralize} from '#/lib/strings/helpers'
|
import {pluralize} from '#/lib/strings/helpers'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
|
@ -21,6 +20,7 @@ import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
|
||||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
|
import {useHaptics} from 'lib/haptics'
|
||||||
import {useProfileShadow} from 'state/cache/profile-shadow'
|
import {useProfileShadow} from 'state/cache/profile-shadow'
|
||||||
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
|
import {ProfileMenu} from '#/view/com/profile/ProfileMenu'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
|
@ -64,6 +64,7 @@ let ProfileHeaderLabeler = ({
|
||||||
const {currentAccount, hasSession} = useSession()
|
const {currentAccount, hasSession} = useSession()
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
const cantSubscribePrompt = Prompt.usePromptControl()
|
const cantSubscribePrompt = Prompt.usePromptControl()
|
||||||
const isSelf = currentAccount?.did === profile.did
|
const isSelf = currentAccount?.did === profile.did
|
||||||
|
|
||||||
|
@ -93,7 +94,7 @@ let ProfileHeaderLabeler = ({
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
if (likeUri) {
|
if (likeUri) {
|
||||||
await unlikeMod({uri: likeUri})
|
await unlikeMod({uri: likeUri})
|
||||||
|
@ -114,7 +115,7 @@ let ProfileHeaderLabeler = ({
|
||||||
)
|
)
|
||||||
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
logger.error(`Failed to toggle labeler like`, {message: e.message})
|
||||||
}
|
}
|
||||||
}, [labeler, likeUri, likeMod, unlikeMod, track, _])
|
}, [labeler, playHaptic, likeUri, unlikeMod, track, likeMod, _])
|
||||||
|
|
||||||
const onPressEditProfile = React.useCallback(() => {
|
const onPressEditProfile = React.useCallback(() => {
|
||||||
track('ProfileHeader:EditProfileButtonClicked')
|
track('ProfileHeader:EditProfileButtonClicked')
|
||||||
|
|
|
@ -2,7 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {defaults, Schema, schema} from '#/state/persisted/schema'
|
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.
|
* The shape of the serialized data from our legacy Mobx store.
|
||||||
|
@ -113,6 +113,7 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
|
||||||
externalEmbeds: defaults.externalEmbeds,
|
externalEmbeds: defaults.externalEmbeds,
|
||||||
lastSelectedHomeFeed: defaults.lastSelectedHomeFeed,
|
lastSelectedHomeFeed: defaults.lastSelectedHomeFeed,
|
||||||
pdsAddressHistory: defaults.pdsAddressHistory,
|
pdsAddressHistory: defaults.pdsAddressHistory,
|
||||||
|
disableHaptics: defaults.disableHaptics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {z} from 'zod'
|
import {z} from 'zod'
|
||||||
|
|
||||||
import {deviceLocales} from '#/platform/detection'
|
import {deviceLocales} from '#/platform/detection'
|
||||||
|
|
||||||
const externalEmbedOptions = ['show', 'hide'] as const
|
const externalEmbedOptions = ['show', 'hide'] as const
|
||||||
|
@ -58,6 +59,7 @@ export const schema = z.object({
|
||||||
useInAppBrowser: z.boolean().optional(),
|
useInAppBrowser: z.boolean().optional(),
|
||||||
lastSelectedHomeFeed: z.string().optional(),
|
lastSelectedHomeFeed: z.string().optional(),
|
||||||
pdsAddressHistory: z.array(z.string()).optional(),
|
pdsAddressHistory: z.array(z.string()).optional(),
|
||||||
|
disableHaptics: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
export type Schema = z.infer<typeof schema>
|
export type Schema = z.infer<typeof schema>
|
||||||
|
|
||||||
|
@ -93,4 +95,5 @@ export const defaults: Schema = {
|
||||||
useInAppBrowser: undefined,
|
useInAppBrowser: undefined,
|
||||||
lastSelectedHomeFeed: undefined,
|
lastSelectedHomeFeed: undefined,
|
||||||
pdsAddressHistory: [],
|
pdsAddressHistory: [],
|
||||||
|
disableHaptics: false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<StateContext>(
|
||||||
|
Boolean(persisted.defaults.disableHaptics),
|
||||||
|
)
|
||||||
|
const setContext = React.createContext<SetContext>((_: 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 (
|
||||||
|
<stateContext.Provider value={state}>
|
||||||
|
<setContext.Provider value={setStateWrapped}>
|
||||||
|
{children}
|
||||||
|
</setContext.Provider>
|
||||||
|
</stateContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHapticsDisabled = () => React.useContext(stateContext)
|
||||||
|
export const useSetHapticsDisabled = () => React.useContext(setContext)
|
|
@ -1,11 +1,12 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {Provider as LanguagesProvider} from './languages'
|
|
||||||
import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required'
|
import {Provider as AltTextRequiredProvider} from '../preferences/alt-text-required'
|
||||||
import {Provider as HiddenPostsProvider} from '../preferences/hidden-posts'
|
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 ExternalEmbedsProvider} from './external-embeds-prefs'
|
||||||
import {Provider as InAppBrowserProvider} from './in-app-browser'
|
import {Provider as InAppBrowserProvider} from './in-app-browser'
|
||||||
|
import {Provider as LanguagesProvider} from './languages'
|
||||||
|
|
||||||
export {useLanguagePrefs, useLanguagePrefsApi} from './languages'
|
|
||||||
export {
|
export {
|
||||||
useRequireAltTextEnabled,
|
useRequireAltTextEnabled,
|
||||||
useSetRequireAltTextEnabled,
|
useSetRequireAltTextEnabled,
|
||||||
|
@ -16,6 +17,7 @@ export {
|
||||||
} from './external-embeds-prefs'
|
} from './external-embeds-prefs'
|
||||||
export * from './hidden-posts'
|
export * from './hidden-posts'
|
||||||
export {useLabelDefinitions} from './label-defs'
|
export {useLabelDefinitions} from './label-defs'
|
||||||
|
export {useLanguagePrefs, useLanguagePrefsApi} from './languages'
|
||||||
|
|
||||||
export function Provider({children}: React.PropsWithChildren<{}>) {
|
export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
return (
|
return (
|
||||||
|
@ -23,7 +25,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
<AltTextRequiredProvider>
|
<AltTextRequiredProvider>
|
||||||
<ExternalEmbedsProvider>
|
<ExternalEmbedsProvider>
|
||||||
<HiddenPostsProvider>
|
<HiddenPostsProvider>
|
||||||
<InAppBrowserProvider>{children}</InAppBrowserProvider>
|
<InAppBrowserProvider>
|
||||||
|
<DisableHapticsProvider>{children}</DisableHapticsProvider>
|
||||||
|
</InAppBrowserProvider>
|
||||||
</HiddenPostsProvider>
|
</HiddenPostsProvider>
|
||||||
</ExternalEmbedsProvider>
|
</ExternalEmbedsProvider>
|
||||||
</AltTextRequiredProvider>
|
</AltTextRequiredProvider>
|
||||||
|
|
|
@ -16,7 +16,6 @@ import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {HITSLOP_10, HITSLOP_20} from '#/lib/constants'
|
import {HITSLOP_10, HITSLOP_20} from '#/lib/constants'
|
||||||
import {Haptics} from '#/lib/haptics'
|
|
||||||
import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons'
|
import {CommentBottomArrow, HeartIcon, HeartIconSolid} from '#/lib/icons'
|
||||||
import {makeProfileLink} from '#/lib/routes/links'
|
import {makeProfileLink} from '#/lib/routes/links'
|
||||||
import {shareUrl} from '#/lib/sharing'
|
import {shareUrl} from '#/lib/sharing'
|
||||||
|
@ -32,6 +31,7 @@ import {
|
||||||
} from '#/state/queries/post'
|
} from '#/state/queries/post'
|
||||||
import {useRequireAuth} from '#/state/session'
|
import {useRequireAuth} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
|
import {useHaptics} from 'lib/haptics'
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
import {useDialogControl} from '#/components/Dialog'
|
||||||
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
|
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
|
||||||
import * as Prompt from '#/components/Prompt'
|
import * as Prompt from '#/components/Prompt'
|
||||||
|
@ -67,6 +67,7 @@ let PostCtrls = ({
|
||||||
)
|
)
|
||||||
const requireAuth = useRequireAuth()
|
const requireAuth = useRequireAuth()
|
||||||
const loggedOutWarningPromptControl = useDialogControl()
|
const loggedOutWarningPromptControl = useDialogControl()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
|
|
||||||
const shouldShowLoggedOutWarning = React.useMemo(() => {
|
const shouldShowLoggedOutWarning = React.useMemo(() => {
|
||||||
return !!post.author.labels?.find(
|
return !!post.author.labels?.find(
|
||||||
|
@ -84,7 +85,7 @@ let PostCtrls = ({
|
||||||
const onPressToggleLike = React.useCallback(async () => {
|
const onPressToggleLike = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
if (!post.viewer?.like) {
|
if (!post.viewer?.like) {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
await queueLike()
|
await queueLike()
|
||||||
} else {
|
} else {
|
||||||
await queueUnlike()
|
await queueUnlike()
|
||||||
|
@ -94,13 +95,13 @@ let PostCtrls = ({
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [post.viewer?.like, queueLike, queueUnlike])
|
}, [playHaptic, post.viewer?.like, queueLike, queueUnlike])
|
||||||
|
|
||||||
const onRepost = useCallback(async () => {
|
const onRepost = useCallback(async () => {
|
||||||
closeModal()
|
closeModal()
|
||||||
try {
|
try {
|
||||||
if (!post.viewer?.repost) {
|
if (!post.viewer?.repost) {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
await queueRepost()
|
await queueRepost()
|
||||||
} else {
|
} else {
|
||||||
await queueUnrepost()
|
await queueUnrepost()
|
||||||
|
@ -110,7 +111,7 @@ let PostCtrls = ({
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [post.viewer?.repost, queueRepost, queueUnrepost, closeModal])
|
}, [closeModal, post.viewer?.repost, playHaptic, queueRepost, queueUnrepost])
|
||||||
|
|
||||||
const onQuote = useCallback(() => {
|
const onQuote = useCallback(() => {
|
||||||
closeModal()
|
closeModal()
|
||||||
|
@ -123,15 +124,16 @@ let PostCtrls = ({
|
||||||
indexedAt: post.indexedAt,
|
indexedAt: post.indexedAt,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
}, [
|
}, [
|
||||||
|
closeModal,
|
||||||
|
openComposer,
|
||||||
post.uri,
|
post.uri,
|
||||||
post.cid,
|
post.cid,
|
||||||
post.author,
|
post.author,
|
||||||
post.indexedAt,
|
post.indexedAt,
|
||||||
record.text,
|
record.text,
|
||||||
openComposer,
|
playHaptic,
|
||||||
closeModal,
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const onShare = useCallback(() => {
|
const onShare = useCallback(() => {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import {truncateAndInvalidate} from '#/state/queries/util'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
import {useComposerControls} from '#/state/shell/composer'
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
import {Haptics} from 'lib/haptics'
|
import {useHaptics} from 'lib/haptics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {ComposeIcon2} from 'lib/icons'
|
import {ComposeIcon2} from 'lib/icons'
|
||||||
|
@ -159,6 +159,7 @@ export function ProfileFeedScreenInner({
|
||||||
const reportDialogControl = useReportDialogControl()
|
const reportDialogControl = useReportDialogControl()
|
||||||
const {openComposer} = useComposerControls()
|
const {openComposer} = useComposerControls()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
const feedSectionRef = React.useRef<SectionRef>(null)
|
const feedSectionRef = React.useRef<SectionRef>(null)
|
||||||
const isScreenFocused = useIsFocused()
|
const isScreenFocused = useIsFocused()
|
||||||
|
|
||||||
|
@ -201,7 +202,7 @@ export function ProfileFeedScreenInner({
|
||||||
|
|
||||||
const onToggleSaved = React.useCallback(async () => {
|
const onToggleSaved = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
if (isSaved) {
|
if (isSaved) {
|
||||||
await removeFeed({uri: feedInfo.uri})
|
await removeFeed({uri: feedInfo.uri})
|
||||||
|
@ -221,18 +222,19 @@ export function ProfileFeedScreenInner({
|
||||||
logger.error('Failed up update feeds', {message: err})
|
logger.error('Failed up update feeds', {message: err})
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
feedInfo,
|
playHaptic,
|
||||||
isSaved,
|
isSaved,
|
||||||
saveFeed,
|
|
||||||
removeFeed,
|
removeFeed,
|
||||||
resetSaveFeed,
|
feedInfo,
|
||||||
resetRemoveFeed,
|
resetRemoveFeed,
|
||||||
_,
|
_,
|
||||||
|
saveFeed,
|
||||||
|
resetSaveFeed,
|
||||||
])
|
])
|
||||||
|
|
||||||
const onTogglePinned = React.useCallback(async () => {
|
const onTogglePinned = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
await unpinFeed({uri: feedInfo.uri})
|
await unpinFeed({uri: feedInfo.uri})
|
||||||
|
@ -245,7 +247,16 @@ export function ProfileFeedScreenInner({
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`))
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed, _])
|
}, [
|
||||||
|
playHaptic,
|
||||||
|
isPinned,
|
||||||
|
unpinFeed,
|
||||||
|
feedInfo,
|
||||||
|
resetUnpinFeed,
|
||||||
|
pinFeed,
|
||||||
|
resetPinFeed,
|
||||||
|
_,
|
||||||
|
])
|
||||||
|
|
||||||
const onPressShare = React.useCallback(() => {
|
const onPressShare = React.useCallback(() => {
|
||||||
const url = toShareUrl(feedInfo.route.href)
|
const url = toShareUrl(feedInfo.route.href)
|
||||||
|
@ -517,6 +528,7 @@ function AboutSection({
|
||||||
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
|
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
|
||||||
const {hasSession} = useSession()
|
const {hasSession} = useSession()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation()
|
const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation()
|
||||||
const {mutateAsync: unlikeFeed, isPending: isUnlikePending} =
|
const {mutateAsync: unlikeFeed, isPending: isUnlikePending} =
|
||||||
useUnlikeMutation()
|
useUnlikeMutation()
|
||||||
|
@ -527,7 +539,7 @@ function AboutSection({
|
||||||
|
|
||||||
const onToggleLiked = React.useCallback(async () => {
|
const onToggleLiked = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
if (isLiked && likeUri) {
|
if (isLiked && likeUri) {
|
||||||
await unlikeFeed({uri: likeUri})
|
await unlikeFeed({uri: likeUri})
|
||||||
|
@ -546,7 +558,7 @@ function AboutSection({
|
||||||
)
|
)
|
||||||
logger.error('Failed up toggle like', {message: err})
|
logger.error('Failed up toggle like', {message: err})
|
||||||
}
|
}
|
||||||
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _])
|
}, [playHaptic, isLiked, likeUri, unlikeFeed, track, likeFeed, feedInfo, _])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.aboutSectionContainer]}>
|
<View style={[styles.aboutSectionContainer]}>
|
||||||
|
|
|
@ -1,69 +1,70 @@
|
||||||
import React, {useCallback, useMemo} from 'react'
|
import React, {useCallback, useMemo} from 'react'
|
||||||
import {Pressable, StyleSheet, View} from 'react-native'
|
import {Pressable, StyleSheet, View} from 'react-native'
|
||||||
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 {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 {useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
|
||||||
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {Feed} from 'view/com/posts/Feed'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {logger} from '#/logger'
|
||||||
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
|
import {isNative, isWeb} from '#/platform/detection'
|
||||||
import {CenteredView} from 'view/com/util/Views'
|
import {listenSoftReset} from '#/state/events'
|
||||||
import {EmptyState} from 'view/com/util/EmptyState'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {LoadingScreen} from 'view/com/util/LoadingScreen'
|
import {
|
||||||
import {RichText} from '#/components/RichText'
|
useListBlockMutation,
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
useListDeleteMutation,
|
||||||
import {TextLink} from 'view/com/util/Link'
|
useListMuteMutation,
|
||||||
import {ListRef} from 'view/com/util/List'
|
useListQuery,
|
||||||
import * as Toast from 'view/com/util/Toast'
|
} from '#/state/queries/list'
|
||||||
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 {FeedDescriptor} from '#/state/queries/post-feed'
|
||||||
|
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
|
||||||
|
import {
|
||||||
|
usePinFeedMutation,
|
||||||
|
usePreferencesQuery,
|
||||||
|
useSetSaveFeedsMutation,
|
||||||
|
useUnpinFeedMutation,
|
||||||
|
} from '#/state/queries/preferences'
|
||||||
|
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 {useHaptics} from 'lib/haptics'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
import {useSetTitle} from 'lib/hooks/useSetTitle'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
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 {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 {ListMembers} from '#/view/com/lists/ListMembers'
|
import {ListMembers} from '#/view/com/lists/ListMembers'
|
||||||
import {Trans, msg} from '@lingui/macro'
|
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
|
||||||
import {useLingui} from '@lingui/react'
|
import {Feed} from 'view/com/posts/Feed'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {EmptyState} from 'view/com/util/EmptyState'
|
||||||
import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
|
import {FAB} from 'view/com/util/fab/FAB'
|
||||||
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
import {
|
import {DropdownItem, NativeDropdown} from 'view/com/util/forms/NativeDropdown'
|
||||||
useListQuery,
|
import {TextLink} from 'view/com/util/Link'
|
||||||
useListMuteMutation,
|
import {ListRef} from 'view/com/util/List'
|
||||||
useListBlockMutation,
|
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
|
||||||
useListDeleteMutation,
|
import {LoadingScreen} from 'view/com/util/LoadingScreen'
|
||||||
} from '#/state/queries/list'
|
import {Text} from 'view/com/util/text/Text'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {useSession} from '#/state/session'
|
import {CenteredView} from 'view/com/util/Views'
|
||||||
import {useComposerControls} from '#/state/shell/composer'
|
|
||||||
import {isNative, isWeb} from '#/platform/detection'
|
|
||||||
import {truncateAndInvalidate} from '#/state/queries/util'
|
|
||||||
import {
|
|
||||||
usePreferencesQuery,
|
|
||||||
usePinFeedMutation,
|
|
||||||
useUnpinFeedMutation,
|
|
||||||
useSetSaveFeedsMutation,
|
|
||||||
} from '#/state/queries/preferences'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
|
||||||
import {listenSoftReset} from '#/state/events'
|
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import * as Prompt from '#/components/Prompt'
|
|
||||||
import {useDialogControl} from '#/components/Dialog'
|
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_CURATE = ['Posts', 'About']
|
||||||
const SECTION_TITLES_MOD = ['About']
|
const SECTION_TITLES_MOD = ['About']
|
||||||
|
@ -254,6 +255,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
||||||
const {data: preferences} = usePreferencesQuery()
|
const {data: preferences} = usePreferencesQuery()
|
||||||
const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
|
const {mutate: setSavedFeeds} = useSetSaveFeedsMutation()
|
||||||
const {track} = useAnalytics()
|
const {track} = useAnalytics()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
|
|
||||||
const deleteListPromptControl = useDialogControl()
|
const deleteListPromptControl = useDialogControl()
|
||||||
const subscribeMutePromptControl = useDialogControl()
|
const subscribeMutePromptControl = useDialogControl()
|
||||||
|
@ -263,7 +265,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
||||||
const isSaved = preferences?.feeds?.saved?.includes(list.uri)
|
const isSaved = preferences?.feeds?.saved?.includes(list.uri)
|
||||||
|
|
||||||
const onTogglePinned = React.useCallback(async () => {
|
const onTogglePinned = React.useCallback(async () => {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isPinned) {
|
if (isPinned) {
|
||||||
|
@ -275,7 +277,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`))
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [list.uri, isPinned, pinFeed, unpinFeed, _])
|
}, [playHaptic, isPinned, unpinFeed, list.uri, pinFeed, _])
|
||||||
|
|
||||||
const onSubscribeMute = useCallback(async () => {
|
const onSubscribeMute = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,31 +1,32 @@
|
||||||
import React from 'react'
|
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 {useFocusEffect} from '@react-navigation/native'
|
||||||
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
import {NativeStackScreenProps} from '@react-navigation/native-stack'
|
||||||
|
|
||||||
import {track} from '#/lib/analytics/analytics'
|
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 {logger} from '#/logger'
|
||||||
import {useSetMinimalShellMode} from '#/state/shell'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {
|
import {
|
||||||
usePreferencesQuery,
|
|
||||||
usePinFeedMutation,
|
usePinFeedMutation,
|
||||||
useUnpinFeedMutation,
|
usePreferencesQuery,
|
||||||
useSetSaveFeedsMutation,
|
useSetSaveFeedsMutation,
|
||||||
|
useUnpinFeedMutation,
|
||||||
} from '#/state/queries/preferences'
|
} from '#/state/queries/preferences'
|
||||||
|
import {useSetMinimalShellMode} from '#/state/shell'
|
||||||
|
import {useAnalytics} from 'lib/analytics/analytics'
|
||||||
|
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 {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 = {
|
const HITSLOP_TOP = {
|
||||||
top: 20,
|
top: 20,
|
||||||
|
@ -189,13 +190,14 @@ function ListItem({
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation()
|
const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation()
|
||||||
const {isPending: isUnpinPending, mutateAsync: unpinFeed} =
|
const {isPending: isUnpinPending, mutateAsync: unpinFeed} =
|
||||||
useUnpinFeedMutation()
|
useUnpinFeedMutation()
|
||||||
const isPending = isPinPending || isUnpinPending
|
const isPending = isPinPending || isUnpinPending
|
||||||
|
|
||||||
const onTogglePinned = React.useCallback(async () => {
|
const onTogglePinned = React.useCallback(async () => {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
resetSaveFeedsMutationState()
|
resetSaveFeedsMutationState()
|
||||||
|
@ -209,7 +211,15 @@ function ListItem({
|
||||||
Toast.show(_(msg`There was an issue contacting the server`))
|
Toast.show(_(msg`There was an issue contacting the server`))
|
||||||
logger.error('Failed to toggle pinned feed', {message: e})
|
logger.error('Failed to toggle pinned feed', {message: e})
|
||||||
}
|
}
|
||||||
}, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState, _])
|
}, [
|
||||||
|
playHaptic,
|
||||||
|
resetSaveFeedsMutationState,
|
||||||
|
isPinned,
|
||||||
|
unpinFeed,
|
||||||
|
feedUri,
|
||||||
|
pinFeed,
|
||||||
|
_,
|
||||||
|
])
|
||||||
|
|
||||||
const onPressUp = React.useCallback(async () => {
|
const onPressUp = React.useCallback(async () => {
|
||||||
if (!isPinned) return
|
if (!isPinned) return
|
||||||
|
|
|
@ -20,10 +20,9 @@ import {useLingui} from '@lingui/react'
|
||||||
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
import {useFocusEffect, useNavigation} from '@react-navigation/native'
|
||||||
import {useQueryClient} from '@tanstack/react-query'
|
import {useQueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
import {isNative} from '#/platform/detection'
|
import {isIOS, isNative} from '#/platform/detection'
|
||||||
import {useModalControls} from '#/state/modals'
|
import {useModalControls} from '#/state/modals'
|
||||||
import {clearLegacyStorage} from '#/state/persisted/legacy'
|
import {clearLegacyStorage} from '#/state/persisted/legacy'
|
||||||
// TODO import {useInviteCodesQuery} from '#/state/queries/invites'
|
|
||||||
import {clear as clearStorage} from '#/state/persisted/store'
|
import {clear as clearStorage} from '#/state/persisted/store'
|
||||||
import {
|
import {
|
||||||
useRequireAltTextEnabled,
|
useRequireAltTextEnabled,
|
||||||
|
@ -57,6 +56,10 @@ import {makeProfileLink} from 'lib/routes/links'
|
||||||
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
|
||||||
import {NavigationProp} from 'lib/routes/types'
|
import {NavigationProp} from 'lib/routes/types'
|
||||||
import {colors, s} from 'lib/styles'
|
import {colors, s} from 'lib/styles'
|
||||||
|
import {
|
||||||
|
useHapticsDisabled,
|
||||||
|
useSetHapticsDisabled,
|
||||||
|
} from 'state/preferences/disable-haptics'
|
||||||
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
|
import {AccountDropdownBtn} from 'view/com/util/AccountDropdownBtn'
|
||||||
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
|
import {SelectableBtn} from 'view/com/util/forms/SelectableBtn'
|
||||||
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
|
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
|
||||||
|
@ -155,6 +158,8 @@ export function SettingsScreen({}: Props) {
|
||||||
const setRequireAltTextEnabled = useSetRequireAltTextEnabled()
|
const setRequireAltTextEnabled = useSetRequireAltTextEnabled()
|
||||||
const inAppBrowserPref = useInAppBrowser()
|
const inAppBrowserPref = useInAppBrowser()
|
||||||
const setUseInAppBrowser = useSetInAppBrowser()
|
const setUseInAppBrowser = useSetInAppBrowser()
|
||||||
|
const isHapticsDisabled = useHapticsDisabled()
|
||||||
|
const setHapticsDisabled = useSetHapticsDisabled()
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
const onboardingDispatch = useOnboardingDispatch()
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
@ -162,9 +167,6 @@ export function SettingsScreen({}: Props) {
|
||||||
const {openModal} = useModalControls()
|
const {openModal} = useModalControls()
|
||||||
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
|
const {isSwitchingAccounts, accounts, currentAccount} = useSession()
|
||||||
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
const {mutate: clearPreferences} = useClearPreferencesMutation()
|
||||||
// TODO
|
|
||||||
// const {data: invites} = useInviteCodesQuery()
|
|
||||||
// const invitesAvailable = invites?.available?.length ?? 0
|
|
||||||
const {setShowLoggedOut} = useLoggedOutViewControls()
|
const {setShowLoggedOut} = useLoggedOutViewControls()
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const exportCarControl = useDialogControl()
|
const exportCarControl = useDialogControl()
|
||||||
|
@ -220,13 +222,6 @@ export function SettingsScreen({}: Props) {
|
||||||
exportCarControl.open()
|
exportCarControl.open()
|
||||||
}, [exportCarControl])
|
}, [exportCarControl])
|
||||||
|
|
||||||
/* TODO
|
|
||||||
const onPressInviteCodes = React.useCallback(() => {
|
|
||||||
track('Settings:InvitecodesButtonClicked')
|
|
||||||
openModal({name: 'invite-codes'})
|
|
||||||
}, [track, openModal])
|
|
||||||
*/
|
|
||||||
|
|
||||||
const onPressLanguageSettings = React.useCallback(() => {
|
const onPressLanguageSettings = React.useCallback(() => {
|
||||||
navigation.navigate('LanguageSettings')
|
navigation.navigate('LanguageSettings')
|
||||||
}, [navigation])
|
}, [navigation])
|
||||||
|
@ -414,58 +409,6 @@ export function SettingsScreen({}: Props) {
|
||||||
|
|
||||||
<View style={styles.spacer20} />
|
<View style={styles.spacer20} />
|
||||||
|
|
||||||
{/* TODO (
|
|
||||||
<>
|
|
||||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
|
||||||
<Trans>Invite a Friend</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
testID="inviteFriendBtn"
|
|
||||||
style={[
|
|
||||||
styles.linkCard,
|
|
||||||
pal.view,
|
|
||||||
isSwitchingAccounts && styles.dimmed,
|
|
||||||
]}
|
|
||||||
onPress={isSwitchingAccounts ? undefined : onPressInviteCodes}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={_(msg`Invite`)}
|
|
||||||
accessibilityHint={_(msg`Opens invite code list`)}
|
|
||||||
disabled={invites?.disabled}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.iconContainer,
|
|
||||||
invitesAvailable > 0 ? primaryBg : pal.btn,
|
|
||||||
]}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="ticket"
|
|
||||||
style={
|
|
||||||
(invitesAvailable > 0
|
|
||||||
? primaryText
|
|
||||||
: pal.text) as FontAwesomeIconStyle
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Text
|
|
||||||
type="lg"
|
|
||||||
style={invitesAvailable > 0 ? pal.link : pal.text}>
|
|
||||||
{invites?.disabled ? (
|
|
||||||
<Trans>
|
|
||||||
Your invite codes are hidden when logged in using an App
|
|
||||||
Password
|
|
||||||
</Trans>
|
|
||||||
) : invitesAvailable === 1 ? (
|
|
||||||
<Trans>{invitesAvailable} invite code available</Trans>
|
|
||||||
) : (
|
|
||||||
<Trans>{invitesAvailable} invite codes available</Trans>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
|
|
||||||
<View style={styles.spacer20} />
|
|
||||||
</>
|
|
||||||
)*/}
|
|
||||||
|
|
||||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
||||||
<Trans>Accessibility</Trans>
|
<Trans>Accessibility</Trans>
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -738,6 +681,19 @@ export function SettingsScreen({}: Props) {
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{isNative && (
|
||||||
|
<View style={[pal.view, styles.toggleCard]}>
|
||||||
|
<ToggleButton
|
||||||
|
type="default-light"
|
||||||
|
label={
|
||||||
|
isIOS ? _(msg`Disable haptics`) : _(msg`Disable vibrations`)
|
||||||
|
}
|
||||||
|
labelType="lg"
|
||||||
|
isSelected={isHapticsDisabled}
|
||||||
|
onPress={() => setHapticsDisabled(!isHapticsDisabled)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
<View style={styles.spacer20} />
|
<View style={styles.spacer20} />
|
||||||
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
<Text type="xl-bold" style={[pal.text, styles.heading]}>
|
||||||
<Trans>Account</Trans>
|
<Trans>Account</Trans>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {BottomTabBarProps} from '@react-navigation/bottom-tabs'
|
||||||
import {StackActions} from '@react-navigation/native'
|
import {StackActions} from '@react-navigation/native'
|
||||||
|
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
import {useAnalytics} from '#/lib/analytics/analytics'
|
||||||
import {Haptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
import {useDedupe} from '#/lib/hooks/useDedupe'
|
import {useDedupe} from '#/lib/hooks/useDedupe'
|
||||||
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
import {useMinimalShellMode} from '#/lib/hooks/useMinimalShellMode'
|
||||||
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
import {useNavigationTabState} from '#/lib/hooks/useNavigationTabState'
|
||||||
|
@ -59,6 +59,7 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
const closeAllActiveElements = useCloseAllActiveElements()
|
const closeAllActiveElements = useCloseAllActiveElements()
|
||||||
const dedupe = useDedupe()
|
const dedupe = useDedupe()
|
||||||
const accountSwitchControl = useDialogControl()
|
const accountSwitchControl = useDialogControl()
|
||||||
|
const playHaptic = useHaptics()
|
||||||
|
|
||||||
const showSignIn = React.useCallback(() => {
|
const showSignIn = React.useCallback(() => {
|
||||||
closeAllActiveElements()
|
closeAllActiveElements()
|
||||||
|
@ -104,9 +105,9 @@ export function BottomBar({navigation}: BottomTabBarProps) {
|
||||||
}, [onPressTab])
|
}, [onPressTab])
|
||||||
|
|
||||||
const onLongPressProfile = React.useCallback(() => {
|
const onLongPressProfile = React.useCallback(() => {
|
||||||
Haptics.default()
|
playHaptic()
|
||||||
accountSwitchControl.open()
|
accountSwitchControl.open()
|
||||||
}, [accountSwitchControl])
|
}, [accountSwitchControl, playHaptic])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
Loading…
Reference in New Issue