mirror of
https://github.com/tooot-app/app
synced 2025-02-18 04:40:57 +01:00
Add haptics and analytics
This commit is contained in:
parent
e765a8fd7c
commit
5473fcb770
@ -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",
|
||||
|
@ -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 (
|
||||
|
@ -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 (
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
|
@ -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')
|
||||
),
|
||||
[]
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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))
|
||||
|
10
src/components/analytics.ts
Normal file
10
src/components/analytics.ts
Normal 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
24
src/components/haptics.ts
Normal 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
|
@ -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}
|
||||
/>
|
||||
|
@ -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 }
|
||||
|
@ -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)
|
||||
}}
|
||||
/>
|
||||
|
@ -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,
|
||||
|
@ -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('发布失败', '', [
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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: '返回重试',
|
||||
|
@ -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} />,
|
||||
|
@ -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(
|
||||
() =>
|
||||
|
@ -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
|
||||
|
@ -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')
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
@ -13,7 +13,7 @@ const initialState = {
|
||||
language: undefined,
|
||||
theme: 'auto',
|
||||
browser: 'internal',
|
||||
analytics: false
|
||||
analytics: true
|
||||
}
|
||||
|
||||
export const changeAnalytics = createAsyncThunk(
|
||||
|
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user