Add haptics and analytics

This commit is contained in:
Zhiyuan Zheng 2020-12-30 14:33:33 +01:00
parent e765a8fd7c
commit 5473fcb770
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
27 changed files with 186 additions and 148 deletions

View File

@ -27,6 +27,7 @@
"expo-firebase-analytics": "~2.6.0",
"expo-firebase-core": "~1.3.0",
"expo-gl": "~9.2.0",
"expo-haptics": "~8.4.0",
"expo-image-picker": "~9.2.0",
"expo-linear-gradient": "~8.4.0",
"expo-linking": "~2.0.0",

View File

@ -202,7 +202,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
inactiveTintColor: localInstance ? theme.secondary : theme.disabled,
showLabel: false
}),
[]
[theme, localInstance]
)
const tabScreenLocalListeners = useCallback(
() => ({
@ -212,7 +212,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
}
}
}),
[]
[localInstance]
)
const tabScreenComposeListeners = useCallback(
({ navigation }) => ({
@ -223,7 +223,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
}
}
}),
[]
[localInstance]
)
const tabScreenComposeComponent = useCallback(() => null, [])
const tabScreenNotificationsListeners = useCallback(
@ -234,7 +234,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
}
}
}),
[]
[localInstance]
)
const tabScreenNotificationsOptions = useMemo(
() => ({
@ -244,7 +244,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
backgroundColor: theme.red
}
}),
[]
[theme, prevNotification]
)
return (

View File

@ -77,7 +77,7 @@ const renderNode = ({
} else {
const domain = href.split(new RegExp(/:\/\/(.[^\/]+)/))
// Need example here
const content = node.children && node.children[0].data
const content = node.children && node.children[0] && node.children[0].data
const shouldBeTag =
tags && tags.filter(tag => `#${tag.name}` === content).length > 0
return (

View File

@ -1,7 +1,3 @@
import React, { useCallback } from 'react'
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
@ -10,10 +6,12 @@ import TimelineCard from '@components/Timelines/Timeline/Shared/Card'
import TimelineContent from '@components/Timelines/Timeline/Shared/Content'
import TimelineHeaderDefault from '@components/Timelines/Timeline/Shared/HeaderDefault'
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
import { useNavigation } from '@react-navigation/native'
import { getLocalAccountId } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import React, { useCallback } from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import { getLocalAccountId } from '@root/utils/slices/instancesSlice'
export interface Props {
item: Mastodon.Status
@ -36,13 +34,6 @@ const TimelineDefault: React.FC<Props> = ({
const navigation = useNavigation()
let actualStatus = item.reblog ? item.reblog : item
const contentWidth = highlighted
? Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides
: Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.M - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right
const onPress = useCallback(
() =>
@ -93,10 +84,7 @@ const TimelineDefault: React.FC<Props> = ({
/>
)}
{actualStatus.media_attachments.length > 0 && (
<TimelineAttachment
status={actualStatus}
contentWidth={contentWidth}
/>
<TimelineAttachment status={actualStatus} />
)}
{actualStatus.card && <TimelineCard card={actualStatus.card} />}
</View>

View File

@ -1,7 +1,3 @@
import React, { useCallback } from 'react'
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
@ -10,10 +6,12 @@ import TimelineCard from '@components/Timelines/Timeline/Shared/Card'
import TimelineContent from '@components/Timelines/Timeline/Shared/Content'
import TimelineHeaderNotification from '@components/Timelines/Timeline/Shared/HeaderNotification'
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
import { useNavigation } from '@react-navigation/native'
import { getLocalAccountId } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import React, { useCallback } from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
import { useSelector } from 'react-redux'
import { getLocalAccountId } from '@root/utils/slices/instancesSlice'
export interface Props {
notification: Mastodon.Notification
@ -31,13 +29,6 @@ const TimelineNotifications: React.FC<Props> = ({
const actualAccount = notification.status
? notification.status.account
: notification.account
const contentWidth = highlighted
? Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 // Global page padding on both sides
: Dimensions.get('window').width -
StyleConstants.Spacing.Global.PagePadding * 2 - // Global page padding on both sides
StyleConstants.Avatar.M - // Avatar width
StyleConstants.Spacing.S // Avatar margin to the right
const onPress = useCallback(
() =>
@ -92,10 +83,7 @@ const TimelineNotifications: React.FC<Props> = ({
/>
)}
{notification.status.media_attachments.length > 0 && (
<TimelineAttachment
status={notification.status}
contentWidth={contentWidth}
/>
<TimelineAttachment status={notification.status} />
)}
{notification.status.card && (
<TimelineCard card={notification.status.card} />

View File

@ -10,6 +10,7 @@ import { StyleConstants } from '@utils/styles/constants'
import { useNavigation } from '@react-navigation/native'
import { findIndex } from 'lodash'
import { TimelineData } from '../../Timeline'
import haptics from '@root/components/haptics'
const fireMutation = async ({
id,
@ -110,12 +111,14 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
return old
})
haptics('Success')
break
}
return oldData
},
onError: (err, _, oldData) => {
haptics('Error')
toast({ type: 'error', content: '请重试' })
queryClient.setQueryData(queryKey, oldData)
}
@ -172,8 +175,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
'com.apple.UIKit.activity.OpenInIBooks'
]
},
() => {},
() => {}
() => haptics('Success'),
() => haptics('Error')
),
[]
)

View File

@ -1,29 +1,30 @@
import Button from '@components/Button'
import AttachmentAudio from '@components/Timelines/Timeline/Shared/Attachment/Audio'
import AttachmentImage from '@components/Timelines/Timeline/Shared/Attachment/Image'
import AttachmentUnsupported from '@components/Timelines/Timeline/Shared/Attachment/Unsupported'
import AttachmentVideo from '@components/Timelines/Timeline/Shared/Attachment/Video'
import { useNavigation } from '@react-navigation/native'
import haptics from '@root/components/haptics'
import { StyleConstants } from '@utils/styles/constants'
import layoutAnimation from '@utils/styles/layoutAnimation'
import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, StyleSheet, View } from 'react-native'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import AttachmentImage from '@root/components/Timelines/Timeline/Shared/Attachment/Image'
import AttachmentVideo from '@root/components/Timelines/Timeline/Shared/Attachment/Video'
import { IImageInfo } from 'react-native-image-zoom-viewer/built/image-viewer.type'
import { useNavigation } from '@react-navigation/native'
import AttachmentUnsupported from './Attachment/Unsupported'
import AttachmentAudio from './Attachment/Audio'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
import Button from '@root/components/Button'
export interface Props {
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
contentWidth: number
}
const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
const { theme } = useTheme()
const TimelineAttachment: React.FC<Props> = ({ status }) => {
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
const onPressBlurView = useCallback(() => {
layoutAnimation()
setSensitiveShown(false)
haptics('Medium')
}, [])
const onPressShow = useCallback(() => {
setSensitiveShown(true)
haptics('Medium')
}, [])
let imageUrls: (IImageInfo & {
@ -65,8 +66,6 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
key={index}
sensitiveShown={sensitiveShown}
video={attachment}
width={contentWidth}
height={(contentWidth / 16) * 9}
/>
)
case 'gifv':
@ -75,8 +74,6 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
key={index}
sensitiveShown={sensitiveShown}
video={attachment}
width={contentWidth}
height={(contentWidth / 16) * 9}
/>
)
case 'audio':
@ -88,7 +85,13 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
/>
)
default:
return <AttachmentUnsupported key={index} attachment={attachment} />
return (
<AttachmentUnsupported
key={index}
sensitiveShown={sensitiveShown}
attachment={attachment}
/>
)
}
}),
[sensitiveShown]
@ -114,7 +117,7 @@ const TimelineAttachment: React.FC<Props> = ({ status, contentWidth }) => {
content='eye-off'
round
overlay
onPress={() => setSensitiveShown(!sensitiveShown)}
onPress={onPressShow}
style={{
position: 'absolute',
top: StyleConstants.Spacing.S,

View File

@ -8,10 +8,14 @@ import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
export interface Props {
sensitiveShown: boolean
attachment: Mastodon.AttachmentUnknown
}
const AttachmentUnsupported: React.FC<Props> = ({ attachment }) => {
const AttachmentUnsupported: React.FC<Props> = ({
sensitiveShown,
attachment
}) => {
const { theme } = useTheme()
return (
<View style={styles.base}>
@ -26,22 +30,26 @@ const AttachmentUnsupported: React.FC<Props> = ({ attachment }) => {
<Blurhash hash={attachment.blurhash} />
</Surface>
) : null}
<Text
style={[
styles.text,
{ color: attachment.blurhash ? theme.background : theme.primary }
]}
>
</Text>
{attachment.remote_url ? (
<Button
type='text'
content='尝试远程链接'
size='S'
overlay
onPress={async () => await openLink(attachment.remote_url!)}
/>
{!sensitiveShown ? (
<>
<Text
style={[
styles.text,
{ color: attachment.blurhash ? theme.background : theme.primary }
]}
>
</Text>
{attachment.remote_url ? (
<Button
type='text'
content='尝试远程链接'
size='S'
overlay
onPress={async () => await openLink(attachment.remote_url!)}
/>
) : null}
</>
) : null}
</View>
)

View File

@ -9,6 +9,7 @@ import relativeTime from '@utils/relativeTime'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
import haptics from '@root/components/haptics'
export interface Props {
queryKey: QueryKey.Timeline
@ -50,10 +51,12 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
pointer: paging.pointer
}))
)
haptics('Success')
return oldData
},
onError: (err, _, oldData) => {
haptics('Error')
toast({ type: 'error', content: '请重试', autoHide: false })
queryClient.setQueryData(queryKey, oldData)
}

View File

@ -3,6 +3,7 @@ import { useMutation, useQueryClient } from 'react-query'
import client from '@api/client'
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
import { toast } from '@components/toast'
import haptics from '@root/components/haptics'
const fireMutation = async ({
type,
@ -69,6 +70,7 @@ const HeaderDefaultActionsAccount: React.FC<Props> = ({
const queryClient = useQueryClient()
const { mutate } = useMutation(fireMutation, {
onSettled: () => {
haptics('Success')
queryKey && queryClient.invalidateQueries(queryKey)
}
})

View File

@ -11,6 +11,7 @@ import relativeTime from '@utils/relativeTime'
import { StyleConstants } from '@utils/styles/constants'
import { relationshipFetch } from '@utils/fetches/relationshipFetch'
import { useTheme } from '@utils/styles/ThemeManager'
import haptics from '@root/components/haptics'
export interface Props {
notification: Mastodon.Notification
@ -67,8 +68,10 @@ const TimelineHeaderNotification: React.FC<Props> = ({ notification }) => {
}).then(res => {
if (res.body.id === (updateData && updateData.id) || data!.id) {
setUpdateData(res.body)
haptics('Success')
return Promise.resolve()
} else {
haptics('Error')
toast({ type: 'error', content: '请重试', autoHide: false })
return Promise.reject()
}

View File

@ -12,6 +12,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
import Emojis from './Emojis'
import { TimelineData } from '../../Timeline'
import { findIndex } from 'lodash'
import haptics from '@root/components/haptics'
const fireMutation = async ({
id,
@ -95,6 +96,8 @@ const TimelinePoll: React.FC<Props> = ({
}
return old
})
haptics('Success')
}
})
@ -207,6 +210,7 @@ const TimelinePoll: React.FC<Props> = ({
<Pressable
style={[styles.optionUnselected]}
onPress={() => {
haptics('Light')
if (poll.multiple) {
setAllOptions(
allOptions.map((o, i) => (i === index ? !o : o))

View File

@ -0,0 +1,10 @@
import * as Analytics from 'expo-firebase-analytics'
import * as Sentry from 'sentry-expo'
const analytics = (event: string, params?: { [key: string]: string }) => {
Analytics.logEvent(event, params).catch(error =>
Sentry.Native.captureException(error)
)
}
export default analytics

24
src/components/haptics.ts Normal file
View File

@ -0,0 +1,24 @@
import * as Haptics from 'expo-haptics'
import * as Sentry from 'sentry-expo'
const haptics = (
type: 'Success' | 'Warning' | 'Error' | 'Light' | 'Medium' | 'Heavy'
) => {
switch (type) {
case 'Success':
case 'Warning':
case 'Error':
Haptics.notificationAsync(Haptics.NotificationFeedbackType[type]).catch(
error => Sentry.Native.captureException(error)
)
break
case 'Light':
case 'Medium':
case 'Heavy':
Haptics.impactAsync(Haptics.ImpactFeedbackStyle[type]).catch(error =>
Sentry.Native.captureException(error)
)
}
}
export default haptics

View File

@ -1,4 +1,19 @@
import Button from '@components/Button'
import ParseContent from '@components/ParseContent'
import { Feather } from '@expo/vector-icons'
import { useNavigation } from '@react-navigation/native'
import analytics from '@root/components/analytics'
import haptics from '@root/components/haptics'
import { applicationFetch } from '@utils/fetches/applicationFetch'
import { instanceFetch } from '@utils/fetches/instanceFetch'
import { loginLocal } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import { useTheme } from '@utils/styles/ThemeManager'
import * as AuthSession from 'expo-auth-session'
import { LinearGradient } from 'expo-linear-gradient'
import { debounce } from 'lodash'
import React, { useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
Dimensions,
Image,
@ -7,24 +22,9 @@ import {
TextInput,
View
} from 'react-native'
import { useQuery } from 'react-query'
import { debounce } from 'lodash'
import { instanceFetch } from '@utils/fetches/instanceFetch'
import * as AuthSession from 'expo-auth-session'
import { useDispatch } from 'react-redux'
import { loginLocal } from '@utils/slices/instancesSlice'
import { useNavigation } from '@react-navigation/native'
import { useTheme } from '@utils/styles/ThemeManager'
import { useTranslation } from 'react-i18next'
import { StyleConstants } from '@utils/styles/constants'
import Button from '@components/Button'
import ParseContent from '@root/components/ParseContent'
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
import { Feather } from '@expo/vector-icons'
import { applicationFetch } from '@root/utils/fetches/applicationFetch'
import { LinearGradient } from 'expo-linear-gradient'
import { useQuery } from 'react-query'
import { useDispatch } from 'react-redux'
const Login: React.FC = () => {
const { t } = useTranslation('meRoot')
@ -99,6 +99,10 @@ const Login: React.FC = () => {
}
)
dispatch(loginLocal({ url: instanceDomain, token: accessToken }))
analytics('login', {
instance: instanceDomain!,
method: 'OAuth2'
})
navigation.navigate('Screen-Local')
}
})()
@ -179,6 +183,7 @@ const Login: React.FC = () => {
instanceQuery.data &&
instanceQuery.data.uri
) {
haptics('Success')
applicationQuery.refetch()
} else {
setInstanceDomain(text)
@ -192,7 +197,10 @@ const Login: React.FC = () => {
<Button
type='text'
content={t('content.login.button')}
onPress={() => applicationQuery.refetch()}
onPress={() => {
haptics('Success')
applicationQuery.refetch()
}}
disabled={!instanceQuery.data?.uri}
loading={instanceQuery.isFetching || applicationQuery.isFetching}
/>

View File

@ -1,12 +1,14 @@
import React from 'react'
import { useDispatch } from 'react-redux'
import { resetLocal } from '@utils/slices/instancesSlice'
import Button from '@components/Button'
import { useNavigation } from '@react-navigation/native'
import analytics from '@root/components/analytics'
import haptics from '@root/components/haptics'
import { resetLocal } from '@utils/slices/instancesSlice'
import { StyleConstants } from '@utils/styles/constants'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { useQueryClient } from 'react-query'
import Button from '@root/components/Button'
import { Alert } from 'react-native'
import { StyleConstants } from '@root/utils/styles/constants'
import { useQueryClient } from 'react-query'
import { useDispatch } from 'react-redux'
const Logout: React.FC = () => {
const { t } = useTranslation('meRoot')
@ -32,8 +34,10 @@ const Logout: React.FC = () => {
text: t('content.logout.alert.buttons.logout'),
style: 'destructive' as const,
onPress: () => {
haptics('Success')
queryClient.clear()
dispatch(resetLocal())
analytics('logout')
navigation.navigate('Screen-Public', {
screen: 'Screen-Public-Root',
params: { publicTab: true }

View File

@ -1,4 +1,5 @@
import { MenuContainer, MenuRow } from '@components/Menu'
import haptics from '@root/components/haptics'
import {
changeAnalytics,
changeBrowser,
@ -52,10 +53,12 @@ const ScreenMeSettings: React.FC = () => {
buttonIndex => {
switch (buttonIndex) {
case 0:
haptics('Success')
dispatch(changeLanguage('zh'))
i18n.changeLanguage('zh')
break
case 1:
haptics('Success')
dispatch(changeLanguage('en'))
i18n.changeLanguage('en')
break
@ -82,13 +85,16 @@ const ScreenMeSettings: React.FC = () => {
buttonIndex => {
switch (buttonIndex) {
case 0:
haptics('Success')
dispatch(changeTheme('auto'))
break
case 1:
haptics('Success')
dispatch(changeTheme('light'))
setTheme('light')
break
case 2:
haptics('Success')
dispatch(changeTheme('dark'))
setTheme('dark')
break
@ -114,9 +120,11 @@ const ScreenMeSettings: React.FC = () => {
buttonIndex => {
switch (buttonIndex) {
case 0:
haptics('Success')
dispatch(changeBrowser('internal'))
break
case 1:
haptics('Success')
dispatch(changeBrowser('external'))
break
}
@ -132,6 +140,7 @@ const ScreenMeSettings: React.FC = () => {
iconBack='chevron-right'
onPress={async () => {
await CacheManager.clearCache()
haptics('Success')
setCacheSize(0)
}}
/>

View File

@ -1,6 +1,7 @@
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'
import client from '@root/api/client'
import Button from '@root/components/Button'
import haptics from '@root/components/haptics'
import ParseContent from '@root/components/ParseContent'
import { announcementFetch } from '@root/utils/fetches/announcementsFetch'
import relativeTime from '@root/utils/relativeTime'
@ -65,6 +66,7 @@ const ScreenSharedAnnouncements: React.FC = ({
})
const queryMutation = useMutation(fireMutation, {
onSettled: () => {
haptics('Success')
refetch()
}
})
@ -280,7 +282,7 @@ const styles = StyleSheet.create({
height: StyleConstants.Font.LineHeight.M
},
reactionText: {
...StyleConstants.FontStyle.M,
...StyleConstants.FontStyle.M
},
reactionCount: {
...StyleConstants.FontStyle.S,

View File

@ -1,4 +1,5 @@
import { HeaderLeft, HeaderRight } from '@components/Header'
import haptics from '@root/components/haptics'
import { toast } from '@root/components/toast'
import { store } from '@root/store'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
@ -114,19 +115,10 @@ const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
case 'reply':
const actualStatus =
params.incomingStatus.reblog || params.incomingStatus
const allMentions = actualStatus.mentions.map(
mention => `@${mention.acct}`
)
let replyPlaceholder = allMentions.join(' ')
if (replyPlaceholder.length === 0) {
replyPlaceholder = `@${actualStatus.account.acct} `
} else {
replyPlaceholder = replyPlaceholder + ' '
}
formatText({
textInput: 'text',
composeDispatch,
content: replyPlaceholder,
content: `@${actualStatus.account.acct} `,
disableDebounce: true
})
break
@ -195,11 +187,13 @@ const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
setIsSubmitting(true)
composeSend(params, composeState)
.then(() => {
haptics('Success')
queryClient.invalidateQueries(['Following'])
navigation.goBack()
toast({ type: 'success', content: '发布成功' })
})
.catch(() => {
haptics('Error')
setIsSubmitting(false)
Alert.alert('发布失败', '', [
{

View File

@ -17,6 +17,7 @@ import {
} from 'react-native'
import { Chase } from 'react-native-animated-spinkit'
import layoutAnimation from '@root/utils/styles/layoutAnimation'
import haptics from '@root/components/haptics'
const DEFAULT_HEIGHT = 200
@ -110,6 +111,7 @@ const ComposeAttachments: React.FC = () => {
type: 'attachment/delete',
payload: item.remote!.id
})
haptics('Success')
}}
/>
<Button

View File

@ -1,4 +1,5 @@
import client from '@root/api/client'
import haptics from '@root/components/haptics'
import { HeaderLeft, HeaderRight } from '@root/components/Header'
import { ComposeContext } from '@screens/Shared/Compose'
import React, {
@ -103,6 +104,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
body: formData
})
.then(() => {
haptics('Success')
Alert.alert('修改成功', '', [
{
text: '好的',
@ -114,6 +116,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
})
.catch(() => {
setIsSubmitting(false)
haptics('Error')
Alert.alert('修改失败', '', [
{
text: '返回重试',

View File

@ -12,6 +12,7 @@ import { useTheme } from '@utils/styles/ThemeManager'
import { ComposeContext } from '@screens/Shared/Compose'
import updateText from './updateText'
import haptics from '@root/components/haptics'
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
const { composeState, composeDispatch } = useContext(ComposeContext)
@ -26,6 +27,7 @@ const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
type: 'emoji',
payload: { ...composeState.emoji, active: false }
})
haptics('Success')
}, [])
const children = useMemo(
() => <Image source={{ uri: emoji.url }} style={styles.emoji} />,

View File

@ -1,4 +1,5 @@
import Emojis from '@components/Timelines/Timeline/Shared/Emojis'
import haptics from '@root/components/haptics'
import { ComposeContext } from '@screens/Shared/Compose'
import ComposeActions from '@screens/Shared/Compose/Actions'
import ComposeRootFooter from '@screens/Shared/Compose/Root/Footer'
@ -57,6 +58,7 @@ const ListItem = React.memo(
newText: item.acct ? `@${item.acct}` : `#${item.name}`,
type: 'suggestion'
})
haptics('Success')
}, [])
const children = useMemo(
() =>

View File

@ -16,21 +16,6 @@ const composeParseState = ({
case 'edit':
return {
...composeInitialState,
...(incomingStatus.spoiler_text?.length && {
spoiler: {
active: true,
count: incomingStatus.spoiler_text.length,
raw: incomingStatus.spoiler_text,
formatted: incomingStatus.spoiler_text,
selection: { start: 0, end: 0 }
}
}),
text: {
count: incomingStatus.text!.length,
raw: incomingStatus.text!,
formatted: undefined,
selection: { start: 0, end: 0 }
},
...(incomingStatus.poll && {
poll: {
active: true,
@ -62,24 +47,8 @@ const composeParseState = ({
}
case 'reply':
const actualStatus = incomingStatus.reblog || incomingStatus
const allMentions = Array.isArray(actualStatus.mentions)
? actualStatus.mentions.map(mention => `@${mention.acct}`)
: []
let replyPlaceholder = allMentions.join(' ')
if (replyPlaceholder.length === 0) {
replyPlaceholder = `@${actualStatus.account.acct} `
} else {
replyPlaceholder = replyPlaceholder + ' '
}
return {
...composeInitialState,
text: {
count: replyPlaceholder.length,
raw: replyPlaceholder,
formatted: undefined,
selection: { start: 0, end: 0 }
},
...(visibilityLock && {
visibility: 'direct',
visibilityLock: true

View File

@ -1,3 +1,4 @@
import haptics from '@root/components/haptics'
import { HeaderLeft, HeaderRight } from '@root/components/Header'
import { StyleConstants } from '@root/utils/styles/constants'
import { findIndex } from 'lodash'
@ -108,8 +109,8 @@ const ScreenSharedImagesViewer: React.FC<Props> = ({
{
url: imageUrls[currentIndex].url
},
() => null,
() => null
() => haptics('Success'),
() => haptics('Error')
)
}
/>

View File

@ -13,7 +13,7 @@ const initialState = {
language: undefined,
theme: 'auto',
browser: 'internal',
analytics: false
analytics: true
}
export const changeAnalytics = createAsyncThunk(

View File

@ -4130,6 +4130,11 @@ expo-gl@~9.2.0:
fbjs "1.0.0"
invariant "^2.2.4"
expo-haptics@~8.4.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/expo-haptics/-/expo-haptics-8.4.0.tgz#8482ebfe3caf4e13c3fe5c762214bcdcb8f24594"
integrity sha512-WnB+uhrYhi0gg8lhkHTrlHZJX3v4ZTH8FXPVr8LewLPzFXQLhyllSfY0V9G/87zNdEk99f/pwC49PtH0b1DBQw==
expo-image-picker@~9.2.0:
version "9.2.1"
resolved "https://registry.yarnpkg.com/expo-image-picker/-/expo-image-picker-9.2.1.tgz#113a46bfc8ef9bf675e8700b1bf7b8472f4de516"