mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Updates
This commit is contained in:
@ -1,22 +1,38 @@
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import analytics from './analytics'
|
||||
import GracefullyImage from './GracefullyImage'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account
|
||||
onPress: () => void
|
||||
onPress?: () => void
|
||||
origin?: string
|
||||
}
|
||||
|
||||
const ComponentAccount: React.FC<Props> = ({ account, onPress }) => {
|
||||
const ComponentAccount: React.FC<Props> = ({
|
||||
account,
|
||||
onPress: customOnPress,
|
||||
origin
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
analytics('search_account_press', { page: origin })
|
||||
navigation.push('Screen-Shared-Account', { account })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.itemDefault, styles.itemAccount]}
|
||||
onPress={onPress}
|
||||
onPress={customOnPress || onPress}
|
||||
>
|
||||
<GracefullyImage
|
||||
uri={{ original: account.avatar_static }}
|
||||
|
@ -18,6 +18,7 @@ import Animated, {
|
||||
useSharedValue,
|
||||
withTiming
|
||||
} from 'react-native-reanimated'
|
||||
import analytics from './analytics'
|
||||
|
||||
export interface Props {
|
||||
children: React.ReactNode
|
||||
@ -42,6 +43,7 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||
}
|
||||
})
|
||||
const callDismiss = () => {
|
||||
analytics('bottomsheet_swipe_close')
|
||||
handleDismiss()
|
||||
}
|
||||
const onGestureEvent = useAnimatedGestureHandler({
|
||||
@ -90,7 +92,10 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||
<Button
|
||||
type='text'
|
||||
content='取消'
|
||||
onPress={() => handleDismiss()}
|
||||
onPress={() => {
|
||||
analytics('bottomsheet_cancel')
|
||||
handleDismiss()
|
||||
}}
|
||||
style={styles.button}
|
||||
/>
|
||||
</Animated.View>
|
||||
|
@ -1,23 +1,39 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Pressable, StyleSheet, Text } from 'react-native'
|
||||
import analytics from './analytics'
|
||||
|
||||
export interface Props {
|
||||
tag: Mastodon.Tag
|
||||
onPress: () => void
|
||||
hashtag: Mastodon.Tag
|
||||
onPress?: () => void
|
||||
origin?: string
|
||||
}
|
||||
|
||||
const ComponentHashtag: React.FC<Props> = ({ tag, onPress }) => {
|
||||
const ComponentHashtag: React.FC<Props> = ({
|
||||
hashtag,
|
||||
onPress: customOnPress,
|
||||
origin
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
analytics('search_account_press', { page: origin })
|
||||
navigation.push('Screen-Shared-Hashtag', { hashtag: hashtag.name })
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.itemDefault, { borderBottomColor: theme.border }]}
|
||||
onPress={onPress}
|
||||
onPress={customOnPress || onPress}
|
||||
>
|
||||
<Text style={[styles.itemHashtag, { color: theme.primary }]}>
|
||||
#{tag.name}
|
||||
#{hashtag.name}
|
||||
</Text>
|
||||
</Pressable>
|
||||
)
|
||||
|
@ -16,6 +16,7 @@ import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import analytics from './analytics'
|
||||
import InstanceAuth from './Instance/Auth'
|
||||
import InstanceInfo from './Instance/Info'
|
||||
import { toast } from './toast'
|
||||
@ -70,6 +71,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
if (instanceDomain) {
|
||||
switch (type) {
|
||||
case 'local':
|
||||
analytics('instance_local_login')
|
||||
if (
|
||||
localInstances &&
|
||||
localInstances.filter(instance => instance.url === instanceDomain)
|
||||
@ -96,6 +98,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
}
|
||||
break
|
||||
case 'remote':
|
||||
analytics('instance_remote_register')
|
||||
haptics('Success')
|
||||
const queryKey: QueryKeyTimeline = [
|
||||
'Timeline',
|
||||
@ -112,6 +115,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
|
||||
const onSubmitEditing = useCallback(
|
||||
({ nativeEvent: { text } }) => {
|
||||
analytics('instance_textinput_submit', { match: text === instanceDomain })
|
||||
if (
|
||||
text === instanceDomain &&
|
||||
instanceQuery.isSuccess &&
|
||||
@ -276,7 +280,10 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
{t('server.disclaimer')}
|
||||
<Text
|
||||
style={{ color: theme.blue }}
|
||||
onPress={() => Linking.openURL('https://tooot.app/privacy')}
|
||||
onPress={() => {
|
||||
analytics('view_privacy')
|
||||
Linking.openURL('https://tooot.app/privacy')
|
||||
}}
|
||||
>
|
||||
https://tooot.app/privacy
|
||||
</Text>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Icon from '@components/Icon'
|
||||
import openLink from '@components/openLink'
|
||||
import ParseEmojis from '@components/Parse/Emojis'
|
||||
import { useNavigation, useRoute } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -29,7 +31,7 @@ const renderNode = ({
|
||||
node: any
|
||||
index: number
|
||||
size: 'M' | 'L'
|
||||
navigation: any
|
||||
navigation: StackNavigationProp<Nav.LocalStackParamList>
|
||||
mentions?: Mastodon.Mention[]
|
||||
tags?: Mastodon.Tag[]
|
||||
showFullLink: boolean
|
||||
@ -53,6 +55,7 @@ const renderNode = ({
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={() => {
|
||||
analytics('status_hashtag_press')
|
||||
!disableDetails &&
|
||||
differentTag &&
|
||||
navigation.push('Screen-Shared-Hashtag', {
|
||||
@ -79,6 +82,7 @@ const renderNode = ({
|
||||
...StyleConstants.FontStyle[size]
|
||||
}}
|
||||
onPress={() => {
|
||||
analytics('status_mention_press')
|
||||
accountIndex !== -1 &&
|
||||
!disableDetails &&
|
||||
differentAccount &&
|
||||
@ -107,13 +111,14 @@ const renderNode = ({
|
||||
...StyleConstants.FontStyle[size],
|
||||
alignItems: 'center'
|
||||
}}
|
||||
onPress={async () =>
|
||||
onPress={async () => {
|
||||
analytics('status_link_press')
|
||||
!disableDetails && !shouldBeTag
|
||||
? await openLink(href)
|
||||
: navigation.push('Screen-Shared-Hashtag', {
|
||||
hashtag: content.substring(1)
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
{!shouldBeTag ? (
|
||||
@ -161,7 +166,9 @@ const ParseHTML: React.FC<Props> = ({
|
||||
expandHint,
|
||||
disableDetails = false
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
const route = useRoute()
|
||||
const { theme } = useTheme()
|
||||
const { t, i18n } = useTranslation('componentParse')
|
||||
@ -229,6 +236,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||
{expandAllow ? (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
analytics('status_readmore', { allow: expandAllow, expanded })
|
||||
layoutAnimation()
|
||||
setExpanded(!expanded)
|
||||
}}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { toast } from '@components/toast'
|
||||
@ -58,26 +59,28 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
|
||||
type='icon'
|
||||
content='X'
|
||||
loading={mutation.isLoading}
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
analytics('relationship_incoming_press_reject')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'incoming',
|
||||
payload: { action: 'reject' }
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
round
|
||||
type='icon'
|
||||
content='Check'
|
||||
loading={mutation.isLoading}
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
analytics('relationship_incoming_press_authorize')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'incoming',
|
||||
payload: { action: 'authorize' }
|
||||
})
|
||||
}
|
||||
}}
|
||||
style={styles.approve}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import { toast } from '@components/toast'
|
||||
@ -40,7 +41,7 @@ const RelationshipOutgoing = React.memo(
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(`button.${action}.function`)
|
||||
function: t(`${action}.function`)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
@ -61,12 +62,17 @@ const RelationshipOutgoing = React.memo(
|
||||
onPress = () => {}
|
||||
} else {
|
||||
if (query.data?.blocked_by) {
|
||||
analytics('relationship_outgoing_blocked_by')
|
||||
content = t('button.blocked_by')
|
||||
onPress = () => null
|
||||
onPress = () => {
|
||||
analytics('relationship_outgoing_blocked_by_press')
|
||||
}
|
||||
} else {
|
||||
if (query.data?.blocking) {
|
||||
analytics('relationship_outgoing_blocking')
|
||||
content = t('button.blocking')
|
||||
onPress = () =>
|
||||
onPress = () => {
|
||||
analytics('relationship_outgoing_blocking_press')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
@ -75,10 +81,13 @@ const RelationshipOutgoing = React.memo(
|
||||
state: query.data?.blocking
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (query.data?.following) {
|
||||
analytics('relationship_outgoing_following')
|
||||
content = t('button.following')
|
||||
onPress = () =>
|
||||
onPress = () => {
|
||||
analytics('relationship_outgoing_following_press')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
@ -87,10 +96,13 @@ const RelationshipOutgoing = React.memo(
|
||||
state: query.data?.following
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (query.data?.requested) {
|
||||
analytics('relationship_outgoing_requested')
|
||||
content = t('button.requested')
|
||||
onPress = () =>
|
||||
onPress = () => {
|
||||
analytics('relationship_outgoing_requested_press')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
@ -99,9 +111,12 @@ const RelationshipOutgoing = React.memo(
|
||||
state: query.data?.requested
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
analytics('relationship_outgoing_default')
|
||||
content = t('button.default')
|
||||
onPress = () =>
|
||||
onPress = () => {
|
||||
analytics('relationship_outgoing_default_press')
|
||||
mutation.mutate({
|
||||
id,
|
||||
type: 'outgoing',
|
||||
@ -110,6 +125,7 @@ const RelationshipOutgoing = React.memo(
|
||||
state: false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { TabView } from 'react-native-tab-view'
|
||||
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
||||
import { useSelector } from 'react-redux'
|
||||
import analytics from './analytics'
|
||||
|
||||
const Stack = createNativeStackNavigator<
|
||||
Nav.LocalStackParamList | Nav.RemoteStackParamList
|
||||
@ -41,6 +42,7 @@ const Timelines: React.FC<Props> = ({ name }) => {
|
||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||
|
||||
const onPressSearch = useCallback(() => {
|
||||
analytics('search_tap', { page: mapNameToContent[name][segment].page })
|
||||
navigation.navigate(`Screen-${name}`, { screen: 'Screen-Shared-Search' })
|
||||
}, [])
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
import ComponentSeparator from '@components/Separator'
|
||||
import TimelineConversation from '@components/Timelines/Timeline/Conversation'
|
||||
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||
import TimelineEnd from '@root/components/Timelines/Timeline/End'
|
||||
import TimelineHeader from '@components/Timelines/Timeline/Header'
|
||||
import TimelineNotifications from '@components/Timelines/Timeline/Notifications'
|
||||
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { getPublicRemoteNotice } from '@utils/slices/contextsSlice'
|
||||
import { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { findIndex } from 'lodash'
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import {
|
||||
FlatListProps,
|
||||
@ -17,9 +14,12 @@ import {
|
||||
} from 'react-native'
|
||||
import { FlatList } from 'react-native-gesture-handler'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import { findIndex } from 'lodash'
|
||||
import { getPublicRemoteNotice } from '@utils/slices/contextsSlice'
|
||||
import TimelineConversation from './Timeline/Conversation'
|
||||
import TimelineDefault from './Timeline/Default'
|
||||
import TimelineEmpty from './Timeline/Empty'
|
||||
import TimelineEnd from './Timeline/End'
|
||||
import TimelineHeader from './Timeline/Header'
|
||||
import TimelineNotifications from './Timeline/Notifications'
|
||||
|
||||
export interface Props {
|
||||
page: App.Pages
|
||||
@ -99,8 +99,10 @@ const Timeline: React.FC<Props> = ({
|
||||
}, [navigation, flattenData])
|
||||
|
||||
const flRef = useRef<FlatList<any>>(null)
|
||||
const scrolled = useRef(false)
|
||||
useEffect(() => {
|
||||
if (toot && isSuccess) {
|
||||
if (toot && isSuccess && !scrolled.current) {
|
||||
scrolled.current = true
|
||||
const pointer = findIndex(flattenData, ['id', toot])
|
||||
setTimeout(() => {
|
||||
flRef.current?.scrollToIndex({
|
||||
@ -109,7 +111,7 @@ const Timeline: React.FC<Props> = ({
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
}, [isSuccess, flattenData])
|
||||
}, [isSuccess, flattenData.length, scrolled])
|
||||
|
||||
const keyExtractor = useCallback(({ id }) => id, [])
|
||||
const renderItem = useCallback(({ item }) => {
|
||||
|
@ -1,5 +1,8 @@
|
||||
import client from '@api/client'
|
||||
import analytics from '@components/analytics'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -9,11 +12,42 @@ import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { useMutation, useQueryClient } from 'react-query'
|
||||
import { useSelector } from 'react-redux'
|
||||
import TimelineActions from './Shared/Actions'
|
||||
import TimelineAvatar from './Shared/Avatar'
|
||||
import TimelineContent from './Shared/Content'
|
||||
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
||||
import TimelinePoll from './Shared/Poll'
|
||||
|
||||
const Avatars: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
marginRight: StyleConstants.Spacing.S,
|
||||
width: StyleConstants.Avatar.M,
|
||||
height: StyleConstants.Avatar.M,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap'
|
||||
}}
|
||||
>
|
||||
{accounts.slice(0, 4).map(account => (
|
||||
<GracefullyImage
|
||||
key={account.id}
|
||||
cache
|
||||
uri={{ original: account.avatar_static }}
|
||||
dimension={{
|
||||
width: StyleConstants.Avatar.M,
|
||||
height:
|
||||
accounts.length > 2
|
||||
? StyleConstants.Avatar.M / 2
|
||||
: StyleConstants.Avatar.M
|
||||
}}
|
||||
style={{ flex: 1, flexBasis: '50%' }}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
conversation: Mastodon.Conversation
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -42,9 +76,11 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
}
|
||||
})
|
||||
|
||||
const navigation = useNavigation()
|
||||
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_conversation_press')
|
||||
if (conversation.last_status) {
|
||||
conversation.unread && mutate()
|
||||
navigation.push('Screen-Shared-Toot', {
|
||||
@ -68,10 +104,7 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
onPress={onPress}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<TimelineAvatar
|
||||
queryKey={queryKey}
|
||||
account={conversation.accounts[0]}
|
||||
/>
|
||||
<Avatars accounts={conversation.accounts} />
|
||||
<TimelineHeaderConversation
|
||||
queryKey={queryKey}
|
||||
conversation={conversation}
|
||||
@ -112,6 +145,7 @@ const TimelineConversation: React.FC<Props> = ({
|
||||
<TimelineActions
|
||||
queryKey={queryKey}
|
||||
status={conversation.last_status}
|
||||
accts={conversation.accounts.map(account => account.acct)}
|
||||
reblog={false}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
||||
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
|
||||
@ -7,6 +8,7 @@ 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 { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -17,6 +19,7 @@ import { useSelector } from 'react-redux'
|
||||
export interface Props {
|
||||
item: Mastodon.Status & { isPinned?: boolean }
|
||||
queryKey?: QueryKeyTimeline
|
||||
origin?: string
|
||||
highlighted?: boolean
|
||||
disableDetails?: boolean
|
||||
disableOnPress?: boolean
|
||||
@ -26,27 +29,42 @@ export interface Props {
|
||||
const TimelineDefault: React.FC<Props> = ({
|
||||
item,
|
||||
queryKey,
|
||||
origin,
|
||||
highlighted = false,
|
||||
disableDetails = false,
|
||||
disableOnPress = false
|
||||
}) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
|
||||
let actualStatus = item.reblog ? item.reblog : item
|
||||
|
||||
const onPress = useCallback(
|
||||
() =>
|
||||
!disableOnPress &&
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_default_press', {
|
||||
page: queryKey ? queryKey[1].page : origin
|
||||
})
|
||||
!disableOnPress &&
|
||||
!highlighted &&
|
||||
navigation.push('Screen-Shared-Toot', {
|
||||
toot: actualStatus
|
||||
}),
|
||||
[]
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable style={styles.statusView} onPress={onPress}>
|
||||
<Pressable
|
||||
style={[
|
||||
styles.statusView,
|
||||
{
|
||||
paddingBottom:
|
||||
disableDetails && disableOnPress
|
||||
? StyleConstants.Spacing.Global.PagePadding
|
||||
: 0
|
||||
}
|
||||
]}
|
||||
onPress={onPress}
|
||||
>
|
||||
{item.reblog ? (
|
||||
<TimelineActioned action='reblog' account={item.account} />
|
||||
) : item.isPinned ? (
|
||||
@ -107,6 +125,11 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
<TimelineActions
|
||||
queryKey={queryKey}
|
||||
status={actualStatus}
|
||||
accts={([actualStatus.account] as Mastodon.Account[] &
|
||||
Mastodon.Mention[])
|
||||
.concat(actualStatus.mentions)
|
||||
.filter(d => d.id !== localAccount?.id)
|
||||
.map(d => d.acct)}
|
||||
reblog={item.reblog ? true : false}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -37,7 +38,10 @@ const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
|
||||
<Button
|
||||
type='text'
|
||||
content={t('empty.error.button')}
|
||||
onPress={() => refetch()}
|
||||
onPress={() => {
|
||||
analytics('timeline_error_press_refetch')
|
||||
refetch()
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import Icon from '@root/components/Icon'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
@ -22,6 +23,7 @@ const TimelineHeader = React.memo(
|
||||
<Text
|
||||
style={{ color: theme.blue }}
|
||||
onPress={() => {
|
||||
analytics('timeline_remote_header_press')
|
||||
dispatch(updatePublicRemoteNotice(1))
|
||||
navigation.navigate('Screen-Me', {
|
||||
screen: 'Screen-Me-Root',
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
||||
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
|
||||
@ -7,6 +8,7 @@ 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 { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -26,19 +28,20 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
highlighted = false
|
||||
}) => {
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
const actualAccount = notification.status
|
||||
? notification.status.account
|
||||
: notification.account
|
||||
|
||||
const onPress = useCallback(
|
||||
() =>
|
||||
notification.status &&
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_notification_press')
|
||||
notification.status &&
|
||||
navigation.push('Screen-Shared-Toot', {
|
||||
toot: notification.status
|
||||
}),
|
||||
[]
|
||||
)
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Pressable style={styles.notificationView} onPress={onPress}>
|
||||
@ -112,6 +115,11 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
<TimelineActions
|
||||
queryKey={queryKey}
|
||||
status={notification.status}
|
||||
accts={([notification.status.account] as Mastodon.Account[] &
|
||||
Mastodon.Mention[])
|
||||
.concat(notification.status.mentions)
|
||||
.filter(d => d.id !== localAccount?.id)
|
||||
.map(d => d.acct)}
|
||||
reblog={false}
|
||||
/>
|
||||
</View>
|
||||
|
@ -1,6 +1,8 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Icon from '@components/Icon'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
@ -20,7 +22,9 @@ const TimelineActioned: React.FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { theme } = useTheme()
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
const name = account.display_name || account.username
|
||||
const iconColor = theme.primary
|
||||
|
||||
@ -29,6 +33,7 @@ const TimelineActioned: React.FC<Props> = ({
|
||||
)
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_shared_actioned_press', { action })
|
||||
navigation.push('Screen-Shared-Account', { account })
|
||||
}, [])
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { toast } from '@components/toast'
|
||||
@ -17,10 +18,16 @@ import { useQueryClient } from 'react-query'
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
status: Mastodon.Status
|
||||
accts: Mastodon.Account['acct'][] // When replying to conversations
|
||||
reblog: boolean
|
||||
}
|
||||
|
||||
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
const TimelineActions: React.FC<Props> = ({
|
||||
queryKey,
|
||||
status,
|
||||
accts,
|
||||
reblog
|
||||
}) => {
|
||||
const navigation = useNavigation()
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { theme } = useTheme()
|
||||
@ -92,63 +99,74 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
}
|
||||
})
|
||||
|
||||
const onPressReply = useCallback(
|
||||
() =>
|
||||
navigation.navigate('Screen-Shared-Compose', {
|
||||
type: 'reply',
|
||||
incomingStatus: status,
|
||||
queryKey
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const onPressReblog = useCallback(
|
||||
() =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'reblogged',
|
||||
currentValue: status.reblogged,
|
||||
propertyCount: 'reblogs_count',
|
||||
countValue: status.reblogs_count
|
||||
}
|
||||
}),
|
||||
[status.reblogged]
|
||||
)
|
||||
const onPressFavourite = useCallback(
|
||||
() =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'favourited',
|
||||
currentValue: status.favourited,
|
||||
propertyCount: 'favourites_count',
|
||||
countValue: status.favourites_count
|
||||
}
|
||||
}),
|
||||
[status.favourited]
|
||||
)
|
||||
const onPressBookmark = useCallback(
|
||||
() =>
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'bookmarked',
|
||||
currentValue: status.bookmarked,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
}),
|
||||
[status.bookmarked]
|
||||
)
|
||||
const onPressReply = useCallback(() => {
|
||||
analytics('timeline_shared_actions_reply_press', {
|
||||
page: queryKey[1].page,
|
||||
count: status.replies_count
|
||||
})
|
||||
navigation.navigate('Screen-Shared-Compose', {
|
||||
type: 'reply',
|
||||
incomingStatus: status,
|
||||
accts,
|
||||
queryKey
|
||||
})
|
||||
}, [status.replies_count])
|
||||
const onPressReblog = useCallback(() => {
|
||||
analytics('timeline_shared_actions_reblog_press', {
|
||||
page: queryKey[1].page,
|
||||
count: status.reblogs_count,
|
||||
current: status.reblogged
|
||||
})
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'reblogged',
|
||||
currentValue: status.reblogged,
|
||||
propertyCount: 'reblogs_count',
|
||||
countValue: status.reblogs_count
|
||||
}
|
||||
})
|
||||
}, [status.reblogged, status.reblogs_count])
|
||||
const onPressFavourite = useCallback(() => {
|
||||
analytics('timeline_shared_actions_favourite_press', {
|
||||
page: queryKey[1].page,
|
||||
count: status.favourites_count,
|
||||
current: status.favourited
|
||||
})
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'favourited',
|
||||
currentValue: status.favourited,
|
||||
propertyCount: 'favourites_count',
|
||||
countValue: status.favourites_count
|
||||
}
|
||||
})
|
||||
}, [status.favourited, status.favourites_count])
|
||||
const onPressBookmark = useCallback(() => {
|
||||
analytics('timeline_shared_actions_bookmark_press', {
|
||||
page: queryKey[1].page,
|
||||
current: status.bookmarked
|
||||
})
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'bookmarked',
|
||||
currentValue: status.bookmarked,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}, [status.bookmarked])
|
||||
|
||||
const childrenReply = useMemo(
|
||||
() => (
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import AttachmentAudio from '@components/Timelines/Timeline/Shared/Attachment/Audio'
|
||||
@ -21,13 +22,15 @@ const TimelineAttachment: React.FC<Props> = ({ status }) => {
|
||||
|
||||
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
||||
const onPressBlurView = useCallback(() => {
|
||||
analytics('timeline_shared_attachment_blurview_press_show')
|
||||
layoutAnimation()
|
||||
setSensitiveShown(false)
|
||||
haptics('Medium')
|
||||
haptics('Light')
|
||||
}, [])
|
||||
const onPressShow = useCallback(() => {
|
||||
analytics('timeline_shared_attachment_blurview_press_hide')
|
||||
setSensitiveShown(true)
|
||||
haptics('Medium')
|
||||
haptics('Light')
|
||||
}, [])
|
||||
|
||||
let imageUrls: (IImageInfo & {
|
||||
|
@ -9,6 +9,7 @@ import { Blurhash } from 'gl-react-blurhash'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import attachmentAspectRatio from './aspectRatio'
|
||||
import analytics from '@components/analytics'
|
||||
|
||||
export interface Props {
|
||||
total: number
|
||||
@ -29,6 +30,7 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||
const [audioPlaying, setAudioPlaying] = useState(false)
|
||||
const [audioPosition, setAudioPosition] = useState(0)
|
||||
const playAudio = useCallback(async () => {
|
||||
analytics('timeline_shared_attachment_audio_play_press', { id: audio.id })
|
||||
if (!audioPlayer) {
|
||||
const { sound } = await Audio.Sound.createAsync(
|
||||
{ uri: audio.url },
|
||||
@ -44,6 +46,7 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||
}
|
||||
}, [audioPlayer, audioPosition])
|
||||
const pauseAudio = useCallback(async () => {
|
||||
analytics('timeline_shared_attachment_audio_pause_press', { id: audio.id })
|
||||
audioPlayer!.pauseAsync()
|
||||
setAudioPlaying(false)
|
||||
}, [audioPlayer])
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { useCallback } from 'react'
|
||||
@ -19,7 +20,10 @@ const AttachmentImage: React.FC<Props> = ({
|
||||
image,
|
||||
navigateToImagesViewer
|
||||
}) => {
|
||||
const onPress = useCallback(() => navigateToImagesViewer(index), [])
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_shared_attachment_image_press', { id: image.id })
|
||||
navigateToImagesViewer(index)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import openLink from '@components/openLink'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -59,9 +60,10 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
||||
content={t('shared.attachment.unsupported.button')}
|
||||
size='S'
|
||||
overlay
|
||||
onPress={async () =>
|
||||
onPress={async () => {
|
||||
analytics('timeline_shared_attachment_unsupported_press')
|
||||
attachment.remote_url && (await openLink(attachment.remote_url))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
|
@ -6,6 +6,7 @@ import { Blurhash } from 'gl-react-blurhash'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import attachmentAspectRatio from './aspectRatio'
|
||||
import analytics from '@components/analytics'
|
||||
|
||||
export interface Props {
|
||||
total: number
|
||||
@ -25,6 +26,13 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||
const [videoLoaded, setVideoLoaded] = useState(false)
|
||||
const [videoPosition, setVideoPosition] = useState<number>(0)
|
||||
const playOnPress = useCallback(async () => {
|
||||
analytics('timeline_shared_attachment_video_length', {
|
||||
length: video.meta?.length
|
||||
})
|
||||
analytics('timeline_shared_attachment_vide_play_press', {
|
||||
id: video.id,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
setVideoLoading(true)
|
||||
if (!videoLoaded) {
|
||||
await videoPlayer.current?.loadAsync({ uri: video.url })
|
||||
@ -66,6 +74,10 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||
useNativeControls={false}
|
||||
onFullscreenUpdate={event => {
|
||||
if (event.fullscreenUpdate === 3) {
|
||||
analytics('timeline_shared_attachment_video_pause_press', {
|
||||
id: video.id,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
videoPlayer.current?.pauseAsync()
|
||||
}
|
||||
}}
|
||||
|
@ -3,6 +3,8 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { StackNavigationProp } from '@react-navigation/stack'
|
||||
import analytics from '@components/analytics'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
@ -10,9 +12,12 @@ export interface Props {
|
||||
}
|
||||
|
||||
const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
|
||||
const navigation = useNavigation()
|
||||
const navigation = useNavigation<
|
||||
StackNavigationProp<Nav.LocalStackParamList>
|
||||
>()
|
||||
// Need to fix go back root
|
||||
const onPress = useCallback(() => {
|
||||
analytics('timeline_shared_avatar_press', { page: queryKey[1].page })
|
||||
queryKey && navigation.push('Screen-Shared-Account', { account })
|
||||
}, [])
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import openLink from '@components/openLink'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
@ -15,7 +16,10 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={[styles.card, { borderColor: theme.border }]}
|
||||
onPress={async () => await openLink(card.url)}
|
||||
onPress={async () => {
|
||||
analytics('timeline_shared_card_press')
|
||||
await openLink(card.url)
|
||||
}}
|
||||
testID='base'
|
||||
>
|
||||
{card.image && (
|
||||
@ -42,7 +46,10 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
||||
{card.description}
|
||||
</Text>
|
||||
) : null}
|
||||
<Text numberOfLines={1} style={{ color: theme.secondary }}>
|
||||
<Text
|
||||
numberOfLines={1}
|
||||
style={[styles.rightLink, { color: theme.secondary }]}
|
||||
>
|
||||
{card.url}
|
||||
</Text>
|
||||
</View>
|
||||
@ -54,14 +61,14 @@ const styles = StyleSheet.create({
|
||||
card: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
height: StyleConstants.Font.LineHeight.M * 4.5,
|
||||
height: StyleConstants.Font.LineHeight.M * 5,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
borderWidth: StyleSheet.hairlineWidth,
|
||||
borderRadius: 6
|
||||
},
|
||||
left: {
|
||||
width: StyleConstants.Font.LineHeight.M * 4.5,
|
||||
height: StyleConstants.Font.LineHeight.M * 4.5
|
||||
width: StyleConstants.Font.LineHeight.M * 5,
|
||||
height: StyleConstants.Font.LineHeight.M * 5
|
||||
},
|
||||
image: {
|
||||
width: '100%',
|
||||
@ -74,11 +81,16 @@ const styles = StyleSheet.create({
|
||||
padding: StyleConstants.Spacing.S
|
||||
},
|
||||
rightTitle: {
|
||||
...StyleConstants.FontStyle.S,
|
||||
marginBottom: StyleConstants.Spacing.XS,
|
||||
fontWeight: StyleConstants.Font.Weight.Bold
|
||||
},
|
||||
rightDescription: {
|
||||
...StyleConstants.FontStyle.S,
|
||||
marginBottom: StyleConstants.Spacing.XS
|
||||
},
|
||||
rightLink: {
|
||||
...StyleConstants.FontStyle.S
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import analytics from '@components/analytics'
|
||||
import haptics from '@components/haptics'
|
||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||
import { toast } from '@components/toast'
|
||||
import {
|
||||
MutationVarsTimelineUpdateAccountProperty,
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
@ -11,7 +13,7 @@ import { useQueryClient } from 'react-query'
|
||||
|
||||
export interface Props {
|
||||
queryKey?: QueryKeyTimeline
|
||||
account: Pick<Mastodon.Account, 'id' | 'acct'>
|
||||
account: Mastodon.Account
|
||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
@ -25,23 +27,30 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||
const queryClient = useQueryClient()
|
||||
const mutateion = useTimelineMutation({
|
||||
queryClient,
|
||||
onSuccess: (_, { payload: { property } }) => {
|
||||
onSuccess: (_, params) => {
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
haptics('Success')
|
||||
toast({
|
||||
type: 'success',
|
||||
message: t('common:toastMessage.success.message', {
|
||||
function: t(`shared.header.actions.account.${property}.function`, {
|
||||
acct: account.acct
|
||||
})
|
||||
function: t(
|
||||
`shared.header.actions.account.${theParams.payload.property}.function`,
|
||||
{
|
||||
acct: account.acct
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
onError: (err: any, { payload: { property } }) => {
|
||||
onError: (err: any, params) => {
|
||||
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||
haptics('Error')
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(`shared.header.actions.account.${property}.function`)
|
||||
function: t(
|
||||
`shared.header.actions.account.${theParams.payload.property}.function`
|
||||
)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
@ -62,6 +71,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||
<MenuHeader heading={t('shared.header.actions.account.heading')} />
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_account_mute_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
@ -77,6 +89,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_account_block_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
@ -92,6 +107,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_account_reports_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutateion.mutate({
|
||||
type: 'updateAccountProperty',
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import MenuContainer from '@components/Menu/Container'
|
||||
import MenuHeader from '@components/Menu/Header'
|
||||
import MenuRow from '@components/Menu/Row'
|
||||
@ -42,6 +43,9 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
||||
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_domain_block_press', {
|
||||
page: queryKey[1].page
|
||||
})
|
||||
Alert.alert(
|
||||
t('shared.header.actions.domain.alert.title', { domain }),
|
||||
t('shared.header.actions.domain.alert.message'),
|
||||
@ -54,6 +58,12 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
||||
text: t('shared.header.actions.domain.alert.buttons.confirm'),
|
||||
style: 'destructive',
|
||||
onPress: () => {
|
||||
analytics(
|
||||
'timeline_shared_headeractions_domain_block_confirm',
|
||||
{
|
||||
page: queryKey && queryKey[1].page
|
||||
}
|
||||
)
|
||||
setBottomSheetVisible(false)
|
||||
mutation.mutate({
|
||||
type: 'domainBlock',
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import BottomSheet from '@components/BottomSheet'
|
||||
import Icon from '@components/Icon'
|
||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||
@ -33,7 +34,12 @@ const HeaderActions = React.memo(
|
||||
const sameDomain = localDomain === statusDomain
|
||||
|
||||
const [modalVisible, setBottomSheetVisible] = useState(false)
|
||||
const onPress = useCallback(() => setBottomSheetVisible(true), [])
|
||||
const onPress = useCallback(() => {
|
||||
analytics('bottomsheet_open_press', {
|
||||
page: queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(true)
|
||||
}, [])
|
||||
const children = useMemo(
|
||||
() => (
|
||||
<Icon
|
||||
|
@ -1,11 +1,7 @@
|
||||
import analytics from '@components/analytics'
|
||||
import MenuContainer from '@components/Menu/Container'
|
||||
import MenuHeader from '@components/Menu/Header'
|
||||
import MenuRow from '@components/Menu/Row'
|
||||
import { toast } from '@components/toast'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Platform, Share } from 'react-native'
|
||||
@ -30,6 +26,7 @@ const HeaderActionsShare: React.FC<Props> = ({
|
||||
iconFront='Share2'
|
||||
title={t(`shared.header.actions.share.${type}.button`)}
|
||||
onPress={async () => {
|
||||
analytics('timeline_shared_headeractions_share_press')
|
||||
switch (Platform.OS) {
|
||||
case 'ios':
|
||||
await Share.share({
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
QueryKeyTimeline,
|
||||
useTimelineMutation
|
||||
} from '@utils/queryHooks/timeline'
|
||||
import analytics from '@components/analytics'
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
@ -37,9 +38,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
function: t(
|
||||
`shared.header.actions.status.${theFunction}.function`
|
||||
)
|
||||
function: t(`shared.header.actions.status.${theFunction}.function`)
|
||||
}),
|
||||
...(err.status &&
|
||||
typeof err.status === 'number' &&
|
||||
@ -55,11 +54,12 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<MenuContainer>
|
||||
<MenuHeader
|
||||
heading={t('shared.header.actions.status.heading')}
|
||||
/>
|
||||
<MenuHeader heading={t('shared.header.actions.status.heading')} />
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_status_delete_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
@ -73,19 +73,31 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_status_deleteedit_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
Alert.alert(
|
||||
t('shared.header.actions.status.edit.alert.title'),
|
||||
t(
|
||||
'shared.header.actions.status.edit.alert.message'
|
||||
),
|
||||
t('shared.header.actions.status.edit.alert.message'),
|
||||
[
|
||||
{ text: t('shared.header.actions.status.edit.alert.buttons.cancel'), style: 'cancel' },
|
||||
{
|
||||
text: t(
|
||||
'shared.header.actions.status.edit.alert.buttons.cancel'
|
||||
),
|
||||
style: 'cancel'
|
||||
},
|
||||
{
|
||||
text: t(
|
||||
'shared.header.actions.status.edit.alert.buttons.confirm'
|
||||
),
|
||||
style: 'destructive',
|
||||
onPress: async () => {
|
||||
analytics(
|
||||
'timeline_shared_headeractions_status_deleteedit_confirm',
|
||||
{
|
||||
page: queryKey && queryKey[1].page
|
||||
}
|
||||
)
|
||||
setBottomSheetVisible(false)
|
||||
const res = await mutation.mutateAsync({
|
||||
type: 'deleteItem',
|
||||
@ -110,46 +122,54 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||
/>
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_status_mute_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
payload: { property: 'muted', currentValue: status.muted }
|
||||
payload: {
|
||||
property: 'muted',
|
||||
currentValue: status.muted,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}}
|
||||
iconFront='VolumeX'
|
||||
title={
|
||||
status.muted
|
||||
? t(
|
||||
'shared.header.actions.status.mute.button.negative'
|
||||
)
|
||||
: t(
|
||||
'shared.header.actions.status.mute.button.positive'
|
||||
)
|
||||
? t('shared.header.actions.status.mute.button.negative')
|
||||
: t('shared.header.actions.status.mute.button.positive')
|
||||
}
|
||||
/>
|
||||
{/* Also note that reblogs cannot be pinned. */}
|
||||
{(status.visibility === 'public' || status.visibility === 'unlisted') && (
|
||||
<MenuRow
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_headeractions_status_pin_press', {
|
||||
page: queryKey && queryKey[1].page
|
||||
})
|
||||
setBottomSheetVisible(false)
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
id: status.id,
|
||||
payload: { property: 'pinned', currentValue: status.pinned }
|
||||
payload: {
|
||||
property: 'pinned',
|
||||
currentValue: status.pinned,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
})
|
||||
}}
|
||||
iconFront='Anchor'
|
||||
title={
|
||||
status.pinned
|
||||
? t(
|
||||
'shared.header.actions.status.pin.button.negative'
|
||||
)
|
||||
: t(
|
||||
'shared.header.actions.status.pin.button.positive'
|
||||
)
|
||||
? t('shared.header.actions.status.pin.button.negative')
|
||||
: t('shared.header.actions.status.pin.button.positive')
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import analytics from '@components/analytics'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { toast } from '@components/toast'
|
||||
import {
|
||||
QueryKeyTimeline,
|
||||
@ -9,12 +11,34 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Pressable, StyleSheet, View } from 'react-native'
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import HeaderSharedAccount from './HeaderShared/Account'
|
||||
import HeaderSharedCreated from './HeaderShared/Created'
|
||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||
|
||||
const Names: React.FC<{ accounts: Mastodon.Account[] }> = ({ accounts }) => {
|
||||
const { t } = useTranslation('componentTimeline')
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Text numberOfLines={1}>
|
||||
<Text style={[styles.namesLeading, { color: theme.secondary }]}>
|
||||
{t('shared.header.conversation.withAccounts')}{' '}
|
||||
</Text>
|
||||
{accounts.map((account, index) => (
|
||||
<Text key={account.id} numberOfLines={1}>
|
||||
{index !== 0 ? ', ' : undefined}
|
||||
<ParseEmojis
|
||||
content={account.display_name || account.username}
|
||||
emojis={account.emojis}
|
||||
fontBold
|
||||
/>
|
||||
</Text>
|
||||
))}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
queryKey: QueryKeyTimeline
|
||||
conversation: Mastodon.Conversation
|
||||
@ -49,16 +73,15 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
|
||||
const { theme } = useTheme()
|
||||
|
||||
const actionOnPress = useCallback(
|
||||
() =>
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'conversations',
|
||||
queryKey,
|
||||
id: conversation.id
|
||||
}),
|
||||
[]
|
||||
)
|
||||
const actionOnPress = useCallback(() => {
|
||||
analytics('timeline_conversation_delete_press')
|
||||
mutation.mutate({
|
||||
type: 'deleteItem',
|
||||
source: 'conversations',
|
||||
queryKey,
|
||||
id: conversation.id
|
||||
})
|
||||
}, [])
|
||||
|
||||
const actionChildren = useMemo(
|
||||
() => (
|
||||
@ -74,7 +97,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
<View style={styles.nameAndMeta}>
|
||||
<HeaderSharedAccount account={conversation.accounts[0]} />
|
||||
<Names accounts={conversation.accounts} />
|
||||
<View style={styles.meta}>
|
||||
{conversation.last_status?.created_at ? (
|
||||
<HeaderSharedCreated
|
||||
@ -100,7 +123,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row'
|
||||
},
|
||||
nameAndMeta: {
|
||||
flex: 4
|
||||
flex: 3
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
@ -115,6 +138,9 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center'
|
||||
},
|
||||
namesLeading: {
|
||||
...StyleConstants.FontStyle.M
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -45,7 +45,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row'
|
||||
},
|
||||
accountAndMeta: {
|
||||
flex: 4
|
||||
flex: 5
|
||||
},
|
||||
meta: {
|
||||
flexDirection: 'row',
|
||||
|
@ -6,7 +6,7 @@ import { StyleSheet, Text, View } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account
|
||||
withoutName?: boolean
|
||||
withoutName?: boolean // For notification follow request etc.
|
||||
}
|
||||
|
||||
const HeaderSharedAccount: React.FC<Props> = ({
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import openLink from '@components/openLink'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
@ -15,9 +16,12 @@ const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
|
||||
|
||||
return application && application.name !== 'Web' ? (
|
||||
<Text
|
||||
onPress={async () =>
|
||||
onPress={async () => {
|
||||
analytics('timeline_shared_header_application_press', {
|
||||
application
|
||||
})
|
||||
application.website && (await openLink(application.website))
|
||||
}
|
||||
}}
|
||||
style={[styles.application, { color: theme.secondary }]}
|
||||
>
|
||||
{t('shared.header.shared.application', { application: application.name })}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import analytics from '@components/analytics'
|
||||
import Button from '@components/Button'
|
||||
import haptics from '@components/haptics'
|
||||
import Icon from '@components/Icon'
|
||||
@ -49,6 +50,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
toast({
|
||||
type: 'error',
|
||||
message: t('common:toastMessage.error.message', {
|
||||
// @ts-ignore
|
||||
function: t(`shared.poll.meta.button.${theParams.payload.type}`)
|
||||
}),
|
||||
...(err.status &&
|
||||
@ -69,7 +71,8 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
return (
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_vote_vote_press')
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
@ -82,7 +85,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
options: allOptions
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
type='text'
|
||||
content={t('shared.poll.meta.button.vote')}
|
||||
loading={mutation.isLoading}
|
||||
@ -94,7 +97,8 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
return (
|
||||
<View style={styles.button}>
|
||||
<Button
|
||||
onPress={() =>
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_vote_refresh_press')
|
||||
mutation.mutate({
|
||||
type: 'updateStatusProperty',
|
||||
queryKey,
|
||||
@ -106,7 +110,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
type: 'refresh'
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
type='text'
|
||||
content={t('shared.poll.meta.button.refresh')}
|
||||
loading={mutation.isLoading}
|
||||
@ -199,6 +203,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||
key={index}
|
||||
style={styles.optionContainer}
|
||||
onPress={() => {
|
||||
analytics('timeline_shared_vote_option_press')
|
||||
haptics('Light')
|
||||
if (poll.multiple) {
|
||||
setAllOptions(allOptions.map((o, i) => (i === index ? !o : o)))
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as Analytics from 'expo-firebase-analytics'
|
||||
|
||||
const analytics = (event: string, params?: { [key: string]: string }) => {
|
||||
const analytics = (event: string, params?: { [key: string]: any }) => {
|
||||
Analytics.logEvent(event, params)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user