1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00

Add haptics and analytics

This commit is contained in:
Zhiyuan Zheng
2020-12-30 14:33:33 +01:00
parent e765a8fd7c
commit 5473fcb770
27 changed files with 186 additions and 148 deletions

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