mirror of https://github.com/tooot-app/app
Updates
This commit is contained in:
parent
2a0ad51b24
commit
08f3036753
|
@ -19,7 +19,8 @@ export default (): ExpoConfig => ({
|
||||||
scheme: 'tooot',
|
scheme: 'tooot',
|
||||||
assetBundlePatterns: ['assets/*'],
|
assetBundlePatterns: ['assets/*'],
|
||||||
extra: {
|
extra: {
|
||||||
sentryDSN: process.env.SENTRY_DSN
|
sentryDSN: process.env.SENTRY_DSN,
|
||||||
|
sentryEnv: process.env.SENTRY_DEPLOY_ENV
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
postPublish: [
|
postPublish: [
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"pages": [
|
"pages": [
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "1",
|
"id": "999",
|
||||||
"created_at": "2021-01-22T03:48:33.901Z",
|
"created_at": "2021-01-22T03:48:33.901Z",
|
||||||
"sensitive": false,
|
"sensitive": false,
|
||||||
"visibility": "public",
|
"visibility": "public",
|
||||||
|
@ -21,17 +21,38 @@
|
||||||
"website": "https://tooot.app"
|
"website": "https://tooot.app"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"id": "1",
|
"id": "999",
|
||||||
"username": "tooot📱",
|
"username": "tooot📱",
|
||||||
"acct": "tooot@xmflsct.com",
|
"acct": "tooot@xmflsct.com",
|
||||||
"display_name": "tooot📱",
|
"display_name": "tooot📱",
|
||||||
"avatar_static": "https://avatars.githubusercontent.com/u/77554750?s=200&v=4"
|
"avatar_static": "https://avatars.githubusercontent.com/u/77554750?s=200&v=4"
|
||||||
},
|
},
|
||||||
"media_attachments": [],
|
"media_attachments": [],
|
||||||
"poll": null
|
"poll": {
|
||||||
|
"id": "1",
|
||||||
|
"expires_at": "2021-02-22T03:48:33.901Z",
|
||||||
|
"expired": false,
|
||||||
|
"multiple": false,
|
||||||
|
"votes_count": 10,
|
||||||
|
"voters_count": null,
|
||||||
|
"voted": false,
|
||||||
|
"own_votes": null,
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"title": "I would love to!",
|
||||||
|
"votes_count": 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Why not give it a go?",
|
||||||
|
"votes_count": 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"emojis": []
|
||||||
|
},
|
||||||
|
"mentions": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "2",
|
"id": "1000",
|
||||||
"created_at": "2021-01-22T03:48:33.901Z",
|
"created_at": "2021-01-22T03:48:33.901Z",
|
||||||
"sensitive": false,
|
"sensitive": false,
|
||||||
"spoiler_text": "",
|
"spoiler_text": "",
|
||||||
|
@ -50,7 +71,7 @@
|
||||||
"website": null
|
"website": null
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"id": "2",
|
"id": "1000",
|
||||||
"username": "Mastodon",
|
"username": "Mastodon",
|
||||||
"acct": "mastodon",
|
"acct": "mastodon",
|
||||||
"display_name": "Mastodon",
|
"display_name": "Mastodon",
|
||||||
|
@ -63,10 +84,11 @@
|
||||||
"description": "Mastodon is an open source decentralized social network - by the people for the people. Join the federation and take back control of your social media!",
|
"description": "Mastodon is an open source decentralized social network - by the people for the people. Join the federation and take back control of your social media!",
|
||||||
"type": "link",
|
"type": "link",
|
||||||
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Mastodon_Logotype_%28Simple%29.svg/1200px-Mastodon_Logotype_%28Simple%29.svg.png"
|
"image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/48/Mastodon_Logotype_%28Simple%29.svg/1200px-Mastodon_Logotype_%28Simple%29.svg.png"
|
||||||
}
|
},
|
||||||
|
"mentions": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "3",
|
"id": "1001",
|
||||||
"created_at": "2021-01-22T03:48:33.901Z",
|
"created_at": "2021-01-22T03:48:33.901Z",
|
||||||
"spoiler_text": "",
|
"spoiler_text": "",
|
||||||
"visibility": "public",
|
"visibility": "public",
|
||||||
|
@ -84,13 +106,70 @@
|
||||||
"website": null
|
"website": null
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"id": "3",
|
"id": "1001",
|
||||||
"username": "Fediverse",
|
"username": "Fediverse",
|
||||||
"acct": "fediverse",
|
"acct": "fediverse",
|
||||||
"display_name": "Fediverse",
|
"display_name": "Fediverse",
|
||||||
"avatar_static": "https://e7.pngegg.com/pngimages/667/514/png-clipart-mastodon-fediverse-social-media-free-software-logo-social-media-blue-text.png"
|
"avatar_static": "https://e7.pngegg.com/pngimages/667/514/png-clipart-mastodon-fediverse-social-media-free-software-logo-social-media-blue-text.png"
|
||||||
},
|
},
|
||||||
"media_attachments": []
|
"media_attachments": [],
|
||||||
|
"mentions": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1002",
|
||||||
|
"created_at": "2021-01-22T03:48:33.901Z",
|
||||||
|
"sensitive": false,
|
||||||
|
"visibility": "public",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"favourited": true,
|
||||||
|
"reblogged": false,
|
||||||
|
"muted": false,
|
||||||
|
"bookmarked": false,
|
||||||
|
"content": "<p>tooot is an open source, simple mobile client for Mastodon. Focusing on your connections while being able to explore the Fediverse.</p>",
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "tooot",
|
||||||
|
"website": "https://tooot.app"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "1002",
|
||||||
|
"username": "tooot📱",
|
||||||
|
"acct": "tooot@xmflsct.com",
|
||||||
|
"display_name": "tooot📱",
|
||||||
|
"avatar_static": "https://avatars.githubusercontent.com/u/77554750?s=200&v=4"
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1003",
|
||||||
|
"created_at": "2021-01-22T03:48:33.901Z",
|
||||||
|
"sensitive": false,
|
||||||
|
"visibility": "public",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"favourited": true,
|
||||||
|
"reblogged": false,
|
||||||
|
"muted": false,
|
||||||
|
"bookmarked": false,
|
||||||
|
"content": "<p>- tooot supports multiple accounts<br />- tooot supports browsing external instance<br />- tooot aims to support multiple languages</p>",
|
||||||
|
"reblog": null,
|
||||||
|
"application": {
|
||||||
|
"name": "tooot",
|
||||||
|
"website": "https://tooot.app"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"id": "1003",
|
||||||
|
"username": "tooot📱",
|
||||||
|
"acct": "tooot@xmflsct.com",
|
||||||
|
"display_name": "tooot📱",
|
||||||
|
"avatar_static": "https://avatars.githubusercontent.com/u/77554750?s=200&v=4"
|
||||||
|
},
|
||||||
|
"media_attachments": [],
|
||||||
|
"mentions": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,13 +9,13 @@ declare namespace Nav {
|
||||||
|
|
||||||
type SharedStackParamList = {
|
type SharedStackParamList = {
|
||||||
'Screen-Shared-Account': {
|
'Screen-Shared-Account': {
|
||||||
account: Pick<Mastodon.Account, 'id' | 'username' | 'acct' | 'url'>
|
account: Mastodon.Account | Mastodon.Mention
|
||||||
}
|
}
|
||||||
'Screen-Shared-Announcements': { showAll?: boolean }
|
'Screen-Shared-Announcements': { showAll?: boolean }
|
||||||
'Screen-Shared-Attachments': { account: Mastodon.Account }
|
'Screen-Shared-Attachments': { account: Mastodon.Account }
|
||||||
'Screen-Shared-Compose':
|
'Screen-Shared-Compose':
|
||||||
| {
|
| {
|
||||||
type: 'reply' | 'conversation' | 'edit'
|
type: 'edit'
|
||||||
incomingStatus: Mastodon.Status
|
incomingStatus: Mastodon.Status
|
||||||
queryKey?: [
|
queryKey?: [
|
||||||
'Timeline',
|
'Timeline',
|
||||||
|
@ -28,6 +28,25 @@ declare namespace Nav {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'reply'
|
||||||
|
incomingStatus: Mastodon.Status
|
||||||
|
accts: Mastodon.Account['acct'][]
|
||||||
|
queryKey?: [
|
||||||
|
'Timeline',
|
||||||
|
{
|
||||||
|
page: App.Pages
|
||||||
|
hashtag?: Mastodon.Tag['name']
|
||||||
|
list?: Mastodon.List['id']
|
||||||
|
toot?: Mastodon.Status['id']
|
||||||
|
account?: Mastodon.Account['id']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'conversation'
|
||||||
|
accts: Mastodon.Account['acct'][]
|
||||||
|
}
|
||||||
| undefined
|
| undefined
|
||||||
'Screen-Shared-Hashtag': {
|
'Screen-Shared-Hashtag': {
|
||||||
hashtag: Mastodon.Tag['name']
|
hashtag: Mastodon.Tag['name']
|
||||||
|
|
|
@ -33,6 +33,7 @@ import React, {
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef
|
useRef
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Image, Platform, StatusBar } from 'react-native'
|
import { Image, Platform, StatusBar } from 'react-native'
|
||||||
import Toast from 'react-native-toast-message'
|
import Toast from 'react-native-toast-message'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
|
@ -70,11 +71,12 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||||
// }, [isConnected, firstRender])
|
// }, [isConnected, firstRender])
|
||||||
|
|
||||||
// On launch display login credentials corrupt information
|
// On launch display login credentials corrupt information
|
||||||
|
const { t } = useTranslation('common')
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const showLocalCorrect = localCorrupt
|
const showLocalCorrect = localCorrupt
|
||||||
? toast({
|
? toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: '登录已过期',
|
message: t('index.localCorrupt'),
|
||||||
description: localCorrupt.length ? localCorrupt : undefined,
|
description: localCorrupt.length ? localCorrupt : undefined,
|
||||||
autoHide: false
|
autoHide: false
|
||||||
})
|
})
|
||||||
|
@ -193,8 +195,8 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||||
<Image
|
<Image
|
||||||
source={{ uri: localAccount?.avatarStatic }}
|
source={{ uri: localAccount?.avatarStatic }}
|
||||||
style={{
|
style={{
|
||||||
width: size + 2,
|
width: size,
|
||||||
height: size + 2,
|
height: size,
|
||||||
borderRadius: size,
|
borderRadius: size,
|
||||||
borderWidth: focused ? 2 : 0,
|
borderWidth: focused ? 2 : 0,
|
||||||
borderColor: focused ? theme.secondary : color
|
borderColor: focused ? theme.secondary : color
|
||||||
|
|
|
@ -1,22 +1,38 @@
|
||||||
import { ParseEmojis } from '@components/Parse'
|
import { ParseEmojis } from '@components/Parse'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
||||||
|
import analytics from './analytics'
|
||||||
import GracefullyImage from './GracefullyImage'
|
import GracefullyImage from './GracefullyImage'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account
|
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 { theme } = useTheme()
|
||||||
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
|
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
analytics('search_account_press', { page: origin })
|
||||||
|
navigation.push('Screen-Shared-Account', { account })
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.itemDefault, styles.itemAccount]}
|
style={[styles.itemDefault, styles.itemAccount]}
|
||||||
onPress={onPress}
|
onPress={customOnPress || onPress}
|
||||||
>
|
>
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
uri={{ original: account.avatar_static }}
|
uri={{ original: account.avatar_static }}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import Animated, {
|
||||||
useSharedValue,
|
useSharedValue,
|
||||||
withTiming
|
withTiming
|
||||||
} from 'react-native-reanimated'
|
} from 'react-native-reanimated'
|
||||||
|
import analytics from './analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
@ -42,6 +43,7 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const callDismiss = () => {
|
const callDismiss = () => {
|
||||||
|
analytics('bottomsheet_swipe_close')
|
||||||
handleDismiss()
|
handleDismiss()
|
||||||
}
|
}
|
||||||
const onGestureEvent = useAnimatedGestureHandler({
|
const onGestureEvent = useAnimatedGestureHandler({
|
||||||
|
@ -90,7 +92,10 @@ const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content='取消'
|
content='取消'
|
||||||
onPress={() => handleDismiss()}
|
onPress={() => {
|
||||||
|
analytics('bottomsheet_cancel')
|
||||||
|
handleDismiss()
|
||||||
|
}}
|
||||||
style={styles.button}
|
style={styles.button}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</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 { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Pressable, StyleSheet, Text } from 'react-native'
|
import { Pressable, StyleSheet, Text } from 'react-native'
|
||||||
|
import analytics from './analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
tag: Mastodon.Tag
|
hashtag: Mastodon.Tag
|
||||||
onPress: () => void
|
onPress?: () => void
|
||||||
|
origin?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ComponentHashtag: React.FC<Props> = ({ tag, onPress }) => {
|
const ComponentHashtag: React.FC<Props> = ({
|
||||||
|
hashtag,
|
||||||
|
onPress: customOnPress,
|
||||||
|
origin
|
||||||
|
}) => {
|
||||||
const { theme } = useTheme()
|
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 (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.itemDefault, { borderBottomColor: theme.border }]}
|
style={[styles.itemDefault, { borderBottomColor: theme.border }]}
|
||||||
onPress={onPress}
|
onPress={customOnPress || onPress}
|
||||||
>
|
>
|
||||||
<Text style={[styles.itemHashtag, { color: theme.primary }]}>
|
<Text style={[styles.itemHashtag, { color: theme.primary }]}>
|
||||||
#{tag.name}
|
#{hashtag.name}
|
||||||
</Text>
|
</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { Placeholder, Fade } from 'rn-placeholder'
|
import { Placeholder, Fade } from 'rn-placeholder'
|
||||||
|
import analytics from './analytics'
|
||||||
import InstanceAuth from './Instance/Auth'
|
import InstanceAuth from './Instance/Auth'
|
||||||
import InstanceInfo from './Instance/Info'
|
import InstanceInfo from './Instance/Info'
|
||||||
import { toast } from './toast'
|
import { toast } from './toast'
|
||||||
|
@ -70,6 +71,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
if (instanceDomain) {
|
if (instanceDomain) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'local':
|
case 'local':
|
||||||
|
analytics('instance_local_login')
|
||||||
if (
|
if (
|
||||||
localInstances &&
|
localInstances &&
|
||||||
localInstances.filter(instance => instance.url === instanceDomain)
|
localInstances.filter(instance => instance.url === instanceDomain)
|
||||||
|
@ -96,6 +98,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case 'remote':
|
case 'remote':
|
||||||
|
analytics('instance_remote_register')
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
const queryKey: QueryKeyTimeline = [
|
const queryKey: QueryKeyTimeline = [
|
||||||
'Timeline',
|
'Timeline',
|
||||||
|
@ -112,6 +115,7 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
|
|
||||||
const onSubmitEditing = useCallback(
|
const onSubmitEditing = useCallback(
|
||||||
({ nativeEvent: { text } }) => {
|
({ nativeEvent: { text } }) => {
|
||||||
|
analytics('instance_textinput_submit', { match: text === instanceDomain })
|
||||||
if (
|
if (
|
||||||
text === instanceDomain &&
|
text === instanceDomain &&
|
||||||
instanceQuery.isSuccess &&
|
instanceQuery.isSuccess &&
|
||||||
|
@ -276,7 +280,10 @@ const ComponentInstance: React.FC<Props> = ({
|
||||||
{t('server.disclaimer')}
|
{t('server.disclaimer')}
|
||||||
<Text
|
<Text
|
||||||
style={{ color: theme.blue }}
|
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
|
https://tooot.app/privacy
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import openLink from '@components/openLink'
|
import openLink from '@components/openLink'
|
||||||
import ParseEmojis from '@components/Parse/Emojis'
|
import ParseEmojis from '@components/Parse/Emojis'
|
||||||
import { useNavigation, useRoute } from '@react-navigation/native'
|
import { useNavigation, useRoute } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -29,7 +31,7 @@ const renderNode = ({
|
||||||
node: any
|
node: any
|
||||||
index: number
|
index: number
|
||||||
size: 'M' | 'L'
|
size: 'M' | 'L'
|
||||||
navigation: any
|
navigation: StackNavigationProp<Nav.LocalStackParamList>
|
||||||
mentions?: Mastodon.Mention[]
|
mentions?: Mastodon.Mention[]
|
||||||
tags?: Mastodon.Tag[]
|
tags?: Mastodon.Tag[]
|
||||||
showFullLink: boolean
|
showFullLink: boolean
|
||||||
|
@ -53,6 +55,7 @@ const renderNode = ({
|
||||||
...StyleConstants.FontStyle[size]
|
...StyleConstants.FontStyle[size]
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('status_hashtag_press')
|
||||||
!disableDetails &&
|
!disableDetails &&
|
||||||
differentTag &&
|
differentTag &&
|
||||||
navigation.push('Screen-Shared-Hashtag', {
|
navigation.push('Screen-Shared-Hashtag', {
|
||||||
|
@ -79,6 +82,7 @@ const renderNode = ({
|
||||||
...StyleConstants.FontStyle[size]
|
...StyleConstants.FontStyle[size]
|
||||||
}}
|
}}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('status_mention_press')
|
||||||
accountIndex !== -1 &&
|
accountIndex !== -1 &&
|
||||||
!disableDetails &&
|
!disableDetails &&
|
||||||
differentAccount &&
|
differentAccount &&
|
||||||
|
@ -107,13 +111,14 @@ const renderNode = ({
|
||||||
...StyleConstants.FontStyle[size],
|
...StyleConstants.FontStyle[size],
|
||||||
alignItems: 'center'
|
alignItems: 'center'
|
||||||
}}
|
}}
|
||||||
onPress={async () =>
|
onPress={async () => {
|
||||||
|
analytics('status_link_press')
|
||||||
!disableDetails && !shouldBeTag
|
!disableDetails && !shouldBeTag
|
||||||
? await openLink(href)
|
? await openLink(href)
|
||||||
: navigation.push('Screen-Shared-Hashtag', {
|
: navigation.push('Screen-Shared-Hashtag', {
|
||||||
hashtag: content.substring(1)
|
hashtag: content.substring(1)
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
{content || (showFullLink ? href : domain[1])}
|
{content || (showFullLink ? href : domain[1])}
|
||||||
{!shouldBeTag ? (
|
{!shouldBeTag ? (
|
||||||
|
@ -161,7 +166,9 @@ const ParseHTML: React.FC<Props> = ({
|
||||||
expandHint,
|
expandHint,
|
||||||
disableDetails = false
|
disableDetails = false
|
||||||
}) => {
|
}) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t, i18n } = useTranslation('componentParse')
|
const { t, i18n } = useTranslation('componentParse')
|
||||||
|
@ -229,6 +236,7 @@ const ParseHTML: React.FC<Props> = ({
|
||||||
{expandAllow ? (
|
{expandAllow ? (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('status_readmore', { allow: expandAllow, expanded })
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
setExpanded(!expanded)
|
setExpanded(!expanded)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
|
@ -58,26 +59,28 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
|
||||||
type='icon'
|
type='icon'
|
||||||
content='X'
|
content='X'
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('relationship_incoming_press_reject')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
payload: { action: 'reject' }
|
payload: { action: 'reject' }
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
round
|
round
|
||||||
type='icon'
|
type='icon'
|
||||||
content='Check'
|
content='Check'
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('relationship_incoming_press_authorize')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'incoming',
|
type: 'incoming',
|
||||||
payload: { action: 'authorize' }
|
payload: { action: 'authorize' }
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
style={styles.approve}
|
style={styles.approve}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
|
@ -40,7 +41,7 @@ const RelationshipOutgoing = React.memo(
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(`button.${action}.function`)
|
function: t(`${action}.function`)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
|
@ -61,12 +62,17 @@ const RelationshipOutgoing = React.memo(
|
||||||
onPress = () => {}
|
onPress = () => {}
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.blocked_by) {
|
if (query.data?.blocked_by) {
|
||||||
|
analytics('relationship_outgoing_blocked_by')
|
||||||
content = t('button.blocked_by')
|
content = t('button.blocked_by')
|
||||||
onPress = () => null
|
onPress = () => {
|
||||||
|
analytics('relationship_outgoing_blocked_by_press')
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.blocking) {
|
if (query.data?.blocking) {
|
||||||
|
analytics('relationship_outgoing_blocking')
|
||||||
content = t('button.blocking')
|
content = t('button.blocking')
|
||||||
onPress = () =>
|
onPress = () => {
|
||||||
|
analytics('relationship_outgoing_blocking_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
|
@ -75,10 +81,13 @@ const RelationshipOutgoing = React.memo(
|
||||||
state: query.data?.blocking
|
state: query.data?.blocking
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.following) {
|
if (query.data?.following) {
|
||||||
|
analytics('relationship_outgoing_following')
|
||||||
content = t('button.following')
|
content = t('button.following')
|
||||||
onPress = () =>
|
onPress = () => {
|
||||||
|
analytics('relationship_outgoing_following_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
|
@ -87,10 +96,13 @@ const RelationshipOutgoing = React.memo(
|
||||||
state: query.data?.following
|
state: query.data?.following
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (query.data?.requested) {
|
if (query.data?.requested) {
|
||||||
|
analytics('relationship_outgoing_requested')
|
||||||
content = t('button.requested')
|
content = t('button.requested')
|
||||||
onPress = () =>
|
onPress = () => {
|
||||||
|
analytics('relationship_outgoing_requested_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
|
@ -99,9 +111,12 @@ const RelationshipOutgoing = React.memo(
|
||||||
state: query.data?.requested
|
state: query.data?.requested
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
analytics('relationship_outgoing_default')
|
||||||
content = t('button.default')
|
content = t('button.default')
|
||||||
onPress = () =>
|
onPress = () => {
|
||||||
|
analytics('relationship_outgoing_default_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id,
|
id,
|
||||||
type: 'outgoing',
|
type: 'outgoing',
|
||||||
|
@ -110,6 +125,7 @@ const RelationshipOutgoing = React.memo(
|
||||||
state: false
|
state: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
import { TabView } from 'react-native-tab-view'
|
import { TabView } from 'react-native-tab-view'
|
||||||
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
import ViewPagerAdapter from 'react-native-tab-view-viewpager-adapter'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
|
import analytics from './analytics'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<
|
const Stack = createNativeStackNavigator<
|
||||||
Nav.LocalStackParamList | Nav.RemoteStackParamList
|
Nav.LocalStackParamList | Nav.RemoteStackParamList
|
||||||
|
@ -41,6 +42,7 @@ const Timelines: React.FC<Props> = ({ name }) => {
|
||||||
const localActiveIndex = useSelector(getLocalActiveIndex)
|
const localActiveIndex = useSelector(getLocalActiveIndex)
|
||||||
|
|
||||||
const onPressSearch = useCallback(() => {
|
const onPressSearch = useCallback(() => {
|
||||||
|
analytics('search_tap', { page: mapNameToContent[name][segment].page })
|
||||||
navigation.navigate(`Screen-${name}`, { screen: 'Screen-Shared-Search' })
|
navigation.navigate(`Screen-${name}`, { screen: 'Screen-Shared-Search' })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import ComponentSeparator from '@components/Separator'
|
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 { 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 { localUpdateNotification } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
import { findIndex } from 'lodash'
|
||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
FlatListProps,
|
FlatListProps,
|
||||||
|
@ -17,9 +14,12 @@ import {
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import { FlatList } from 'react-native-gesture-handler'
|
import { FlatList } from 'react-native-gesture-handler'
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from 'react-redux'
|
||||||
import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline'
|
import TimelineConversation from './Timeline/Conversation'
|
||||||
import { findIndex } from 'lodash'
|
import TimelineDefault from './Timeline/Default'
|
||||||
import { getPublicRemoteNotice } from '@utils/slices/contextsSlice'
|
import TimelineEmpty from './Timeline/Empty'
|
||||||
|
import TimelineEnd from './Timeline/End'
|
||||||
|
import TimelineHeader from './Timeline/Header'
|
||||||
|
import TimelineNotifications from './Timeline/Notifications'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
page: App.Pages
|
page: App.Pages
|
||||||
|
@ -99,8 +99,10 @@ const Timeline: React.FC<Props> = ({
|
||||||
}, [navigation, flattenData])
|
}, [navigation, flattenData])
|
||||||
|
|
||||||
const flRef = useRef<FlatList<any>>(null)
|
const flRef = useRef<FlatList<any>>(null)
|
||||||
|
const scrolled = useRef(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (toot && isSuccess) {
|
if (toot && isSuccess && !scrolled.current) {
|
||||||
|
scrolled.current = true
|
||||||
const pointer = findIndex(flattenData, ['id', toot])
|
const pointer = findIndex(flattenData, ['id', toot])
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
flRef.current?.scrollToIndex({
|
flRef.current?.scrollToIndex({
|
||||||
|
@ -109,7 +111,7 @@ const Timeline: React.FC<Props> = ({
|
||||||
})
|
})
|
||||||
}, 500)
|
}, 500)
|
||||||
}
|
}
|
||||||
}, [isSuccess, flattenData])
|
}, [isSuccess, flattenData.length, scrolled])
|
||||||
|
|
||||||
const keyExtractor = useCallback(({ id }) => id, [])
|
const keyExtractor = useCallback(({ id }) => id, [])
|
||||||
const renderItem = useCallback(({ item }) => {
|
const renderItem = useCallback(({ item }) => {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import client from '@api/client'
|
import client from '@api/client'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -9,11 +12,42 @@ import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import { useMutation, useQueryClient } from 'react-query'
|
import { useMutation, useQueryClient } from 'react-query'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import TimelineActions from './Shared/Actions'
|
import TimelineActions from './Shared/Actions'
|
||||||
import TimelineAvatar from './Shared/Avatar'
|
|
||||||
import TimelineContent from './Shared/Content'
|
import TimelineContent from './Shared/Content'
|
||||||
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
import TimelineHeaderConversation from './Shared/HeaderConversation'
|
||||||
import TimelinePoll from './Shared/Poll'
|
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 {
|
export interface Props {
|
||||||
conversation: Mastodon.Conversation
|
conversation: Mastodon.Conversation
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
|
@ -42,9 +76,11 @@ const TimelineConversation: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('timeline_conversation_press')
|
||||||
if (conversation.last_status) {
|
if (conversation.last_status) {
|
||||||
conversation.unread && mutate()
|
conversation.unread && mutate()
|
||||||
navigation.push('Screen-Shared-Toot', {
|
navigation.push('Screen-Shared-Toot', {
|
||||||
|
@ -68,10 +104,7 @@ const TimelineConversation: React.FC<Props> = ({
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<TimelineAvatar
|
<Avatars accounts={conversation.accounts} />
|
||||||
queryKey={queryKey}
|
|
||||||
account={conversation.accounts[0]}
|
|
||||||
/>
|
|
||||||
<TimelineHeaderConversation
|
<TimelineHeaderConversation
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
|
@ -112,6 +145,7 @@ const TimelineConversation: React.FC<Props> = ({
|
||||||
<TimelineActions
|
<TimelineActions
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
status={conversation.last_status}
|
status={conversation.last_status}
|
||||||
|
accts={conversation.accounts.map(account => account.acct)}
|
||||||
reblog={false}
|
reblog={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
||||||
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||||
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
|
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 TimelineHeaderDefault from '@components/Timelines/Timeline/Shared/HeaderDefault'
|
||||||
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
|
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -17,6 +19,7 @@ import { useSelector } from 'react-redux'
|
||||||
export interface Props {
|
export interface Props {
|
||||||
item: Mastodon.Status & { isPinned?: boolean }
|
item: Mastodon.Status & { isPinned?: boolean }
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
|
origin?: string
|
||||||
highlighted?: boolean
|
highlighted?: boolean
|
||||||
disableDetails?: boolean
|
disableDetails?: boolean
|
||||||
disableOnPress?: boolean
|
disableOnPress?: boolean
|
||||||
|
@ -26,27 +29,42 @@ export interface Props {
|
||||||
const TimelineDefault: React.FC<Props> = ({
|
const TimelineDefault: React.FC<Props> = ({
|
||||||
item,
|
item,
|
||||||
queryKey,
|
queryKey,
|
||||||
|
origin,
|
||||||
highlighted = false,
|
highlighted = false,
|
||||||
disableDetails = false,
|
disableDetails = false,
|
||||||
disableOnPress = false
|
disableOnPress = false
|
||||||
}) => {
|
}) => {
|
||||||
const localAccount = useSelector(getLocalAccount)
|
const localAccount = useSelector(getLocalAccount)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
|
|
||||||
let actualStatus = item.reblog ? item.reblog : item
|
let actualStatus = item.reblog ? item.reblog : item
|
||||||
|
|
||||||
const onPress = useCallback(
|
const onPress = useCallback(() => {
|
||||||
() =>
|
analytics('timeline_default_press', {
|
||||||
!disableOnPress &&
|
page: queryKey ? queryKey[1].page : origin
|
||||||
|
})
|
||||||
|
!disableOnPress &&
|
||||||
!highlighted &&
|
!highlighted &&
|
||||||
navigation.push('Screen-Shared-Toot', {
|
navigation.push('Screen-Shared-Toot', {
|
||||||
toot: actualStatus
|
toot: actualStatus
|
||||||
}),
|
})
|
||||||
[]
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable style={styles.statusView} onPress={onPress}>
|
<Pressable
|
||||||
|
style={[
|
||||||
|
styles.statusView,
|
||||||
|
{
|
||||||
|
paddingBottom:
|
||||||
|
disableDetails && disableOnPress
|
||||||
|
? StyleConstants.Spacing.Global.PagePadding
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
onPress={onPress}
|
||||||
|
>
|
||||||
{item.reblog ? (
|
{item.reblog ? (
|
||||||
<TimelineActioned action='reblog' account={item.account} />
|
<TimelineActioned action='reblog' account={item.account} />
|
||||||
) : item.isPinned ? (
|
) : item.isPinned ? (
|
||||||
|
@ -107,6 +125,11 @@ const TimelineDefault: React.FC<Props> = ({
|
||||||
<TimelineActions
|
<TimelineActions
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
status={actualStatus}
|
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}
|
reblog={item.reblog ? true : false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -37,7 +38,10 @@ const TimelineEmpty: React.FC<Props> = ({ status, refetch }) => {
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={t('empty.error.button')}
|
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 { useNavigation } from '@react-navigation/native'
|
||||||
import Icon from '@root/components/Icon'
|
import Icon from '@root/components/Icon'
|
||||||
import { StyleConstants } from '@root/utils/styles/constants'
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
|
@ -22,6 +23,7 @@ const TimelineHeader = React.memo(
|
||||||
<Text
|
<Text
|
||||||
style={{ color: theme.blue }}
|
style={{ color: theme.blue }}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_remote_header_press')
|
||||||
dispatch(updatePublicRemoteNotice(1))
|
dispatch(updatePublicRemoteNotice(1))
|
||||||
navigation.navigate('Screen-Me', {
|
navigation.navigate('Screen-Me', {
|
||||||
screen: 'Screen-Me-Root',
|
screen: 'Screen-Me-Root',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
import TimelineActioned from '@components/Timelines/Timeline/Shared/Actioned'
|
||||||
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
import TimelineActions from '@components/Timelines/Timeline/Shared/Actions'
|
||||||
import TimelineAttachment from '@components/Timelines/Timeline/Shared/Attachment'
|
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 TimelineHeaderNotification from '@components/Timelines/Timeline/Shared/HeaderNotification'
|
||||||
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
|
import TimelinePoll from '@components/Timelines/Timeline/Shared/Poll'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -26,19 +28,20 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||||
highlighted = false
|
highlighted = false
|
||||||
}) => {
|
}) => {
|
||||||
const localAccount = useSelector(getLocalAccount)
|
const localAccount = useSelector(getLocalAccount)
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const actualAccount = notification.status
|
const actualAccount = notification.status
|
||||||
? notification.status.account
|
? notification.status.account
|
||||||
: notification.account
|
: notification.account
|
||||||
|
|
||||||
const onPress = useCallback(
|
const onPress = useCallback(() => {
|
||||||
() =>
|
analytics('timeline_notification_press')
|
||||||
notification.status &&
|
notification.status &&
|
||||||
navigation.push('Screen-Shared-Toot', {
|
navigation.push('Screen-Shared-Toot', {
|
||||||
toot: notification.status
|
toot: notification.status
|
||||||
}),
|
})
|
||||||
[]
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pressable style={styles.notificationView} onPress={onPress}>
|
<Pressable style={styles.notificationView} onPress={onPress}>
|
||||||
|
@ -112,6 +115,11 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||||
<TimelineActions
|
<TimelineActions
|
||||||
queryKey={queryKey}
|
queryKey={queryKey}
|
||||||
status={notification.status}
|
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}
|
reblog={false}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { ParseEmojis } from '@components/Parse'
|
import { ParseEmojis } from '@components/Parse'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
@ -20,7 +22,9 @@ const TimelineActioned: React.FC<Props> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const name = account.display_name || account.username
|
const name = account.display_name || account.username
|
||||||
const iconColor = theme.primary
|
const iconColor = theme.primary
|
||||||
|
|
||||||
|
@ -29,6 +33,7 @@ const TimelineActioned: React.FC<Props> = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('timeline_shared_actioned_press', { action })
|
||||||
navigation.push('Screen-Shared-Account', { account })
|
navigation.push('Screen-Shared-Account', { account })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
|
@ -17,10 +18,16 @@ import { useQueryClient } from 'react-query'
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
status: Mastodon.Status
|
status: Mastodon.Status
|
||||||
|
accts: Mastodon.Account['acct'][] // When replying to conversations
|
||||||
reblog: boolean
|
reblog: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
const TimelineActions: React.FC<Props> = ({
|
||||||
|
queryKey,
|
||||||
|
status,
|
||||||
|
accts,
|
||||||
|
reblog
|
||||||
|
}) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
@ -92,63 +99,74 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPressReply = useCallback(
|
const onPressReply = useCallback(() => {
|
||||||
() =>
|
analytics('timeline_shared_actions_reply_press', {
|
||||||
navigation.navigate('Screen-Shared-Compose', {
|
page: queryKey[1].page,
|
||||||
type: 'reply',
|
count: status.replies_count
|
||||||
incomingStatus: status,
|
})
|
||||||
queryKey
|
navigation.navigate('Screen-Shared-Compose', {
|
||||||
}),
|
type: 'reply',
|
||||||
[]
|
incomingStatus: status,
|
||||||
)
|
accts,
|
||||||
const onPressReblog = useCallback(
|
queryKey
|
||||||
() =>
|
})
|
||||||
mutation.mutate({
|
}, [status.replies_count])
|
||||||
type: 'updateStatusProperty',
|
const onPressReblog = useCallback(() => {
|
||||||
queryKey,
|
analytics('timeline_shared_actions_reblog_press', {
|
||||||
id: status.id,
|
page: queryKey[1].page,
|
||||||
reblog,
|
count: status.reblogs_count,
|
||||||
payload: {
|
current: status.reblogged
|
||||||
property: 'reblogged',
|
})
|
||||||
currentValue: status.reblogged,
|
mutation.mutate({
|
||||||
propertyCount: 'reblogs_count',
|
type: 'updateStatusProperty',
|
||||||
countValue: status.reblogs_count
|
queryKey,
|
||||||
}
|
id: status.id,
|
||||||
}),
|
reblog,
|
||||||
[status.reblogged]
|
payload: {
|
||||||
)
|
property: 'reblogged',
|
||||||
const onPressFavourite = useCallback(
|
currentValue: status.reblogged,
|
||||||
() =>
|
propertyCount: 'reblogs_count',
|
||||||
mutation.mutate({
|
countValue: status.reblogs_count
|
||||||
type: 'updateStatusProperty',
|
}
|
||||||
queryKey,
|
})
|
||||||
id: status.id,
|
}, [status.reblogged, status.reblogs_count])
|
||||||
reblog,
|
const onPressFavourite = useCallback(() => {
|
||||||
payload: {
|
analytics('timeline_shared_actions_favourite_press', {
|
||||||
property: 'favourited',
|
page: queryKey[1].page,
|
||||||
currentValue: status.favourited,
|
count: status.favourites_count,
|
||||||
propertyCount: 'favourites_count',
|
current: status.favourited
|
||||||
countValue: status.favourites_count
|
})
|
||||||
}
|
mutation.mutate({
|
||||||
}),
|
type: 'updateStatusProperty',
|
||||||
[status.favourited]
|
queryKey,
|
||||||
)
|
id: status.id,
|
||||||
const onPressBookmark = useCallback(
|
reblog,
|
||||||
() =>
|
payload: {
|
||||||
mutation.mutate({
|
property: 'favourited',
|
||||||
type: 'updateStatusProperty',
|
currentValue: status.favourited,
|
||||||
queryKey,
|
propertyCount: 'favourites_count',
|
||||||
id: status.id,
|
countValue: status.favourites_count
|
||||||
reblog,
|
}
|
||||||
payload: {
|
})
|
||||||
property: 'bookmarked',
|
}, [status.favourited, status.favourites_count])
|
||||||
currentValue: status.bookmarked,
|
const onPressBookmark = useCallback(() => {
|
||||||
propertyCount: undefined,
|
analytics('timeline_shared_actions_bookmark_press', {
|
||||||
countValue: undefined
|
page: queryKey[1].page,
|
||||||
}
|
current: status.bookmarked
|
||||||
}),
|
})
|
||||||
[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(
|
const childrenReply = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import AttachmentAudio from '@components/Timelines/Timeline/Shared/Attachment/Audio'
|
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 [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
||||||
const onPressBlurView = useCallback(() => {
|
const onPressBlurView = useCallback(() => {
|
||||||
|
analytics('timeline_shared_attachment_blurview_press_show')
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
setSensitiveShown(false)
|
setSensitiveShown(false)
|
||||||
haptics('Medium')
|
haptics('Light')
|
||||||
}, [])
|
}, [])
|
||||||
const onPressShow = useCallback(() => {
|
const onPressShow = useCallback(() => {
|
||||||
|
analytics('timeline_shared_attachment_blurview_press_hide')
|
||||||
setSensitiveShown(true)
|
setSensitiveShown(true)
|
||||||
haptics('Medium')
|
haptics('Light')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
let imageUrls: (IImageInfo & {
|
let imageUrls: (IImageInfo & {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Blurhash } from 'gl-react-blurhash'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
total: number
|
total: number
|
||||||
|
@ -29,6 +30,7 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||||
const [audioPlaying, setAudioPlaying] = useState(false)
|
const [audioPlaying, setAudioPlaying] = useState(false)
|
||||||
const [audioPosition, setAudioPosition] = useState(0)
|
const [audioPosition, setAudioPosition] = useState(0)
|
||||||
const playAudio = useCallback(async () => {
|
const playAudio = useCallback(async () => {
|
||||||
|
analytics('timeline_shared_attachment_audio_play_press', { id: audio.id })
|
||||||
if (!audioPlayer) {
|
if (!audioPlayer) {
|
||||||
const { sound } = await Audio.Sound.createAsync(
|
const { sound } = await Audio.Sound.createAsync(
|
||||||
{ uri: audio.url },
|
{ uri: audio.url },
|
||||||
|
@ -44,6 +46,7 @@ const AttachmentAudio: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [audioPlayer, audioPosition])
|
}, [audioPlayer, audioPosition])
|
||||||
const pauseAudio = useCallback(async () => {
|
const pauseAudio = useCallback(async () => {
|
||||||
|
analytics('timeline_shared_attachment_audio_pause_press', { id: audio.id })
|
||||||
audioPlayer!.pauseAsync()
|
audioPlayer!.pauseAsync()
|
||||||
setAudioPlaying(false)
|
setAudioPlaying(false)
|
||||||
}, [audioPlayer])
|
}, [audioPlayer])
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
|
@ -19,7 +20,10 @@ const AttachmentImage: React.FC<Props> = ({
|
||||||
image,
|
image,
|
||||||
navigateToImagesViewer
|
navigateToImagesViewer
|
||||||
}) => {
|
}) => {
|
||||||
const onPress = useCallback(() => navigateToImagesViewer(index), [])
|
const onPress = useCallback(() => {
|
||||||
|
analytics('timeline_shared_attachment_image_press', { id: image.id })
|
||||||
|
navigateToImagesViewer(index)
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GracefullyImage
|
<GracefullyImage
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import openLink from '@components/openLink'
|
import openLink from '@components/openLink'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -59,9 +60,10 @@ const AttachmentUnsupported: React.FC<Props> = ({
|
||||||
content={t('shared.attachment.unsupported.button')}
|
content={t('shared.attachment.unsupported.button')}
|
||||||
size='S'
|
size='S'
|
||||||
overlay
|
overlay
|
||||||
onPress={async () =>
|
onPress={async () => {
|
||||||
|
analytics('timeline_shared_attachment_unsupported_press')
|
||||||
attachment.remote_url && (await openLink(attachment.remote_url))
|
attachment.remote_url && (await openLink(attachment.remote_url))
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Blurhash } from 'gl-react-blurhash'
|
||||||
import React, { useCallback, useRef, useState } from 'react'
|
import React, { useCallback, useRef, useState } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, StyleSheet, View } from 'react-native'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
total: number
|
total: number
|
||||||
|
@ -25,6 +26,13 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
const [videoLoaded, setVideoLoaded] = useState(false)
|
const [videoLoaded, setVideoLoaded] = useState(false)
|
||||||
const [videoPosition, setVideoPosition] = useState<number>(0)
|
const [videoPosition, setVideoPosition] = useState<number>(0)
|
||||||
const playOnPress = useCallback(async () => {
|
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)
|
setVideoLoading(true)
|
||||||
if (!videoLoaded) {
|
if (!videoLoaded) {
|
||||||
await videoPlayer.current?.loadAsync({ uri: video.url })
|
await videoPlayer.current?.loadAsync({ uri: video.url })
|
||||||
|
@ -66,6 +74,10 @@ const AttachmentVideo: React.FC<Props> = ({
|
||||||
useNativeControls={false}
|
useNativeControls={false}
|
||||||
onFullscreenUpdate={event => {
|
onFullscreenUpdate={event => {
|
||||||
if (event.fullscreenUpdate === 3) {
|
if (event.fullscreenUpdate === 3) {
|
||||||
|
analytics('timeline_shared_attachment_video_pause_press', {
|
||||||
|
id: video.id,
|
||||||
|
timestamp: Date.now()
|
||||||
|
})
|
||||||
videoPlayer.current?.pauseAsync()
|
videoPlayer.current?.pauseAsync()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
|
@ -10,9 +12,12 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
|
const TimelineAvatar: React.FC<Props> = ({ queryKey, account }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
// Need to fix go back root
|
// Need to fix go back root
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('timeline_shared_avatar_press', { page: queryKey[1].page })
|
||||||
queryKey && navigation.push('Screen-Shared-Account', { account })
|
queryKey && navigation.push('Screen-Shared-Account', { account })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import openLink from '@components/openLink'
|
import openLink from '@components/openLink'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -15,7 +16,10 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={[styles.card, { borderColor: theme.border }]}
|
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'
|
testID='base'
|
||||||
>
|
>
|
||||||
{card.image && (
|
{card.image && (
|
||||||
|
@ -42,7 +46,10 @@ const TimelineCard: React.FC<Props> = ({ card }) => {
|
||||||
{card.description}
|
{card.description}
|
||||||
</Text>
|
</Text>
|
||||||
) : null}
|
) : null}
|
||||||
<Text numberOfLines={1} style={{ color: theme.secondary }}>
|
<Text
|
||||||
|
numberOfLines={1}
|
||||||
|
style={[styles.rightLink, { color: theme.secondary }]}
|
||||||
|
>
|
||||||
{card.url}
|
{card.url}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
@ -54,14 +61,14 @@ const styles = StyleSheet.create({
|
||||||
card: {
|
card: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
height: StyleConstants.Font.LineHeight.M * 4.5,
|
height: StyleConstants.Font.LineHeight.M * 5,
|
||||||
marginTop: StyleConstants.Spacing.M,
|
marginTop: StyleConstants.Spacing.M,
|
||||||
borderWidth: StyleSheet.hairlineWidth,
|
borderWidth: StyleSheet.hairlineWidth,
|
||||||
borderRadius: 6
|
borderRadius: 6
|
||||||
},
|
},
|
||||||
left: {
|
left: {
|
||||||
width: StyleConstants.Font.LineHeight.M * 4.5,
|
width: StyleConstants.Font.LineHeight.M * 5,
|
||||||
height: StyleConstants.Font.LineHeight.M * 4.5
|
height: StyleConstants.Font.LineHeight.M * 5
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -74,11 +81,16 @@ const styles = StyleSheet.create({
|
||||||
padding: StyleConstants.Spacing.S
|
padding: StyleConstants.Spacing.S
|
||||||
},
|
},
|
||||||
rightTitle: {
|
rightTitle: {
|
||||||
|
...StyleConstants.FontStyle.S,
|
||||||
marginBottom: StyleConstants.Spacing.XS,
|
marginBottom: StyleConstants.Spacing.XS,
|
||||||
fontWeight: StyleConstants.Font.Weight.Bold
|
fontWeight: StyleConstants.Font.Weight.Bold
|
||||||
},
|
},
|
||||||
rightDescription: {
|
rightDescription: {
|
||||||
|
...StyleConstants.FontStyle.S,
|
||||||
marginBottom: StyleConstants.Spacing.XS
|
marginBottom: StyleConstants.Spacing.XS
|
||||||
|
},
|
||||||
|
rightLink: {
|
||||||
|
...StyleConstants.FontStyle.S
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
import { MenuContainer, MenuHeader, MenuRow } from '@components/Menu'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
import {
|
import {
|
||||||
|
MutationVarsTimelineUpdateAccountProperty,
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
} from '@utils/queryHooks/timeline'
|
} from '@utils/queryHooks/timeline'
|
||||||
|
@ -11,7 +13,7 @@ import { useQueryClient } from 'react-query'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey?: QueryKeyTimeline
|
queryKey?: QueryKeyTimeline
|
||||||
account: Pick<Mastodon.Account, 'id' | 'acct'>
|
account: Mastodon.Account
|
||||||
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
setBottomSheetVisible: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,23 +27,30 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutateion = useTimelineMutation({
|
const mutateion = useTimelineMutation({
|
||||||
queryClient,
|
queryClient,
|
||||||
onSuccess: (_, { payload: { property } }) => {
|
onSuccess: (_, params) => {
|
||||||
|
const theParams = params as MutationVarsTimelineUpdateAccountProperty
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
toast({
|
toast({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
message: t('common:toastMessage.success.message', {
|
message: t('common:toastMessage.success.message', {
|
||||||
function: t(`shared.header.actions.account.${property}.function`, {
|
function: t(
|
||||||
acct: account.acct
|
`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')
|
haptics('Error')
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
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 &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
|
@ -62,6 +71,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||||
<MenuHeader heading={t('shared.header.actions.account.heading')} />
|
<MenuHeader heading={t('shared.header.actions.account.heading')} />
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_account_mute_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutateion.mutate({
|
mutateion.mutate({
|
||||||
type: 'updateAccountProperty',
|
type: 'updateAccountProperty',
|
||||||
|
@ -77,6 +89,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_account_block_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutateion.mutate({
|
mutateion.mutate({
|
||||||
type: 'updateAccountProperty',
|
type: 'updateAccountProperty',
|
||||||
|
@ -92,6 +107,9 @@ const HeaderActionsAccount: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_account_reports_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutateion.mutate({
|
mutateion.mutate({
|
||||||
type: 'updateAccountProperty',
|
type: 'updateAccountProperty',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import MenuContainer from '@components/Menu/Container'
|
import MenuContainer from '@components/Menu/Container'
|
||||||
import MenuHeader from '@components/Menu/Header'
|
import MenuHeader from '@components/Menu/Header'
|
||||||
import MenuRow from '@components/Menu/Row'
|
import MenuRow from '@components/Menu/Row'
|
||||||
|
@ -42,6 +43,9 @@ const HeaderActionsDomain: React.FC<Props> = ({
|
||||||
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
|
<MenuHeader heading={t(`shared.header.actions.domain.heading`)} />
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_domain_block_press', {
|
||||||
|
page: queryKey[1].page
|
||||||
|
})
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('shared.header.actions.domain.alert.title', { domain }),
|
t('shared.header.actions.domain.alert.title', { domain }),
|
||||||
t('shared.header.actions.domain.alert.message'),
|
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'),
|
text: t('shared.header.actions.domain.alert.buttons.confirm'),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
|
analytics(
|
||||||
|
'timeline_shared_headeractions_domain_block_confirm',
|
||||||
|
{
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
}
|
||||||
|
)
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'domainBlock',
|
type: 'domainBlock',
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import BottomSheet from '@components/BottomSheet'
|
import BottomSheet from '@components/BottomSheet'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
||||||
|
@ -33,7 +34,12 @@ const HeaderActions = React.memo(
|
||||||
const sameDomain = localDomain === statusDomain
|
const sameDomain = localDomain === statusDomain
|
||||||
|
|
||||||
const [modalVisible, setBottomSheetVisible] = useState(false)
|
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(
|
const children = useMemo(
|
||||||
() => (
|
() => (
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import MenuContainer from '@components/Menu/Container'
|
import MenuContainer from '@components/Menu/Container'
|
||||||
import MenuHeader from '@components/Menu/Header'
|
import MenuHeader from '@components/Menu/Header'
|
||||||
import MenuRow from '@components/Menu/Row'
|
import MenuRow from '@components/Menu/Row'
|
||||||
import { toast } from '@components/toast'
|
|
||||||
import {
|
|
||||||
QueryKeyTimeline,
|
|
||||||
useTimelineMutation
|
|
||||||
} from '@utils/queryHooks/timeline'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, Share } from 'react-native'
|
import { Platform, Share } from 'react-native'
|
||||||
|
@ -30,6 +26,7 @@ const HeaderActionsShare: React.FC<Props> = ({
|
||||||
iconFront='Share2'
|
iconFront='Share2'
|
||||||
title={t(`shared.header.actions.share.${type}.button`)}
|
title={t(`shared.header.actions.share.${type}.button`)}
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
|
analytics('timeline_shared_headeractions_share_press')
|
||||||
switch (Platform.OS) {
|
switch (Platform.OS) {
|
||||||
case 'ios':
|
case 'ios':
|
||||||
await Share.share({
|
await Share.share({
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
useTimelineMutation
|
useTimelineMutation
|
||||||
} from '@utils/queryHooks/timeline'
|
} from '@utils/queryHooks/timeline'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
|
@ -37,9 +38,7 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
function: t(
|
function: t(`shared.header.actions.status.${theFunction}.function`)
|
||||||
`shared.header.actions.status.${theFunction}.function`
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
|
@ -55,11 +54,12 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
<MenuHeader
|
<MenuHeader heading={t('shared.header.actions.status.heading')} />
|
||||||
heading={t('shared.header.actions.status.heading')}
|
|
||||||
/>
|
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_status_delete_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'deleteItem',
|
type: 'deleteItem',
|
||||||
|
@ -73,19 +73,31 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_status_deleteedit_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
t('shared.header.actions.status.edit.alert.title'),
|
t('shared.header.actions.status.edit.alert.title'),
|
||||||
t(
|
t('shared.header.actions.status.edit.alert.message'),
|
||||||
'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(
|
text: t(
|
||||||
'shared.header.actions.status.edit.alert.buttons.confirm'
|
'shared.header.actions.status.edit.alert.buttons.confirm'
|
||||||
),
|
),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
|
analytics(
|
||||||
|
'timeline_shared_headeractions_status_deleteedit_confirm',
|
||||||
|
{
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
}
|
||||||
|
)
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
const res = await mutation.mutateAsync({
|
const res = await mutation.mutateAsync({
|
||||||
type: 'deleteItem',
|
type: 'deleteItem',
|
||||||
|
@ -110,46 +122,54 @@ const HeaderActionsStatus: React.FC<Props> = ({
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_status_mute_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
payload: { property: 'muted', currentValue: status.muted }
|
payload: {
|
||||||
|
property: 'muted',
|
||||||
|
currentValue: status.muted,
|
||||||
|
propertyCount: undefined,
|
||||||
|
countValue: undefined
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
iconFront='VolumeX'
|
iconFront='VolumeX'
|
||||||
title={
|
title={
|
||||||
status.muted
|
status.muted
|
||||||
? t(
|
? t('shared.header.actions.status.mute.button.negative')
|
||||||
'shared.header.actions.status.mute.button.negative'
|
: t('shared.header.actions.status.mute.button.positive')
|
||||||
)
|
|
||||||
: t(
|
|
||||||
'shared.header.actions.status.mute.button.positive'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{/* Also note that reblogs cannot be pinned. */}
|
{/* Also note that reblogs cannot be pinned. */}
|
||||||
{(status.visibility === 'public' || status.visibility === 'unlisted') && (
|
{(status.visibility === 'public' || status.visibility === 'unlisted') && (
|
||||||
<MenuRow
|
<MenuRow
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_headeractions_status_pin_press', {
|
||||||
|
page: queryKey && queryKey[1].page
|
||||||
|
})
|
||||||
setBottomSheetVisible(false)
|
setBottomSheetVisible(false)
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
id: status.id,
|
id: status.id,
|
||||||
payload: { property: 'pinned', currentValue: status.pinned }
|
payload: {
|
||||||
|
property: 'pinned',
|
||||||
|
currentValue: status.pinned,
|
||||||
|
propertyCount: undefined,
|
||||||
|
countValue: undefined
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
iconFront='Anchor'
|
iconFront='Anchor'
|
||||||
title={
|
title={
|
||||||
status.pinned
|
status.pinned
|
||||||
? t(
|
? t('shared.header.actions.status.pin.button.negative')
|
||||||
'shared.header.actions.status.pin.button.negative'
|
: t('shared.header.actions.status.pin.button.positive')
|
||||||
)
|
|
||||||
: t(
|
|
||||||
'shared.header.actions.status.pin.button.positive'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
import { ParseEmojis } from '@components/Parse'
|
||||||
import { toast } from '@components/toast'
|
import { toast } from '@components/toast'
|
||||||
import {
|
import {
|
||||||
QueryKeyTimeline,
|
QueryKeyTimeline,
|
||||||
|
@ -9,12 +11,34 @@ import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
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 { useQueryClient } from 'react-query'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
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 {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
conversation: Mastodon.Conversation
|
conversation: Mastodon.Conversation
|
||||||
|
@ -49,16 +73,15 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||||
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const actionOnPress = useCallback(
|
const actionOnPress = useCallback(() => {
|
||||||
() =>
|
analytics('timeline_conversation_delete_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'deleteItem',
|
type: 'deleteItem',
|
||||||
source: 'conversations',
|
source: 'conversations',
|
||||||
queryKey,
|
queryKey,
|
||||||
id: conversation.id
|
id: conversation.id
|
||||||
}),
|
})
|
||||||
[]
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
const actionChildren = useMemo(
|
const actionChildren = useMemo(
|
||||||
() => (
|
() => (
|
||||||
|
@ -74,7 +97,7 @@ const HeaderConversation: React.FC<Props> = ({ queryKey, conversation }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<View style={styles.nameAndMeta}>
|
<View style={styles.nameAndMeta}>
|
||||||
<HeaderSharedAccount account={conversation.accounts[0]} />
|
<Names accounts={conversation.accounts} />
|
||||||
<View style={styles.meta}>
|
<View style={styles.meta}>
|
||||||
{conversation.last_status?.created_at ? (
|
{conversation.last_status?.created_at ? (
|
||||||
<HeaderSharedCreated
|
<HeaderSharedCreated
|
||||||
|
@ -100,7 +123,7 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
nameAndMeta: {
|
nameAndMeta: {
|
||||||
flex: 4
|
flex: 3
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
@ -115,6 +138,9 @@ const styles = StyleSheet.create({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
namesLeading: {
|
||||||
|
...StyleConstants.FontStyle.M
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ const styles = StyleSheet.create({
|
||||||
flexDirection: 'row'
|
flexDirection: 'row'
|
||||||
},
|
},
|
||||||
accountAndMeta: {
|
accountAndMeta: {
|
||||||
flex: 4
|
flex: 5
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { StyleSheet, Text, View } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account
|
account: Mastodon.Account
|
||||||
withoutName?: boolean
|
withoutName?: boolean // For notification follow request etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderSharedAccount: React.FC<Props> = ({
|
const HeaderSharedAccount: React.FC<Props> = ({
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import openLink from '@components/openLink'
|
import openLink from '@components/openLink'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -15,9 +16,12 @@ const HeaderSharedApplication: React.FC<Props> = ({ application }) => {
|
||||||
|
|
||||||
return application && application.name !== 'Web' ? (
|
return application && application.name !== 'Web' ? (
|
||||||
<Text
|
<Text
|
||||||
onPress={async () =>
|
onPress={async () => {
|
||||||
|
analytics('timeline_shared_header_application_press', {
|
||||||
|
application
|
||||||
|
})
|
||||||
application.website && (await openLink(application.website))
|
application.website && (await openLink(application.website))
|
||||||
}
|
}}
|
||||||
style={[styles.application, { color: theme.secondary }]}
|
style={[styles.application, { color: theme.secondary }]}
|
||||||
>
|
>
|
||||||
{t('shared.header.shared.application', { application: application.name })}
|
{t('shared.header.shared.application', { application: application.name })}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
@ -49,6 +50,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
toast({
|
toast({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:toastMessage.error.message', {
|
message: t('common:toastMessage.error.message', {
|
||||||
|
// @ts-ignore
|
||||||
function: t(`shared.poll.meta.button.${theParams.payload.type}`)
|
function: t(`shared.poll.meta.button.${theParams.payload.type}`)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
|
@ -69,7 +71,8 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
return (
|
return (
|
||||||
<View style={styles.button}>
|
<View style={styles.button}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_vote_vote_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
|
@ -82,7 +85,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
options: allOptions
|
options: allOptions
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
type='text'
|
type='text'
|
||||||
content={t('shared.poll.meta.button.vote')}
|
content={t('shared.poll.meta.button.vote')}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
|
@ -94,7 +97,8 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
return (
|
return (
|
||||||
<View style={styles.button}>
|
<View style={styles.button}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_vote_refresh_press')
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
type: 'updateStatusProperty',
|
type: 'updateStatusProperty',
|
||||||
queryKey,
|
queryKey,
|
||||||
|
@ -106,7 +110,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
type: 'refresh'
|
type: 'refresh'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
type='text'
|
type='text'
|
||||||
content={t('shared.poll.meta.button.refresh')}
|
content={t('shared.poll.meta.button.refresh')}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
|
@ -199,6 +203,7 @@ const TimelinePoll: React.FC<Props> = ({
|
||||||
key={index}
|
key={index}
|
||||||
style={styles.optionContainer}
|
style={styles.optionContainer}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_vote_option_press')
|
||||||
haptics('Light')
|
haptics('Light')
|
||||||
if (poll.multiple) {
|
if (poll.multiple) {
|
||||||
setAllOptions(allOptions.map((o, i) => (i === index ? !o : o)))
|
setAllOptions(allOptions.map((o, i) => (i === index ? !o : o)))
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as Analytics from 'expo-firebase-analytics'
|
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)
|
Analytics.logEvent(event, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export default {
|
export default {
|
||||||
|
index: {
|
||||||
|
localCorrupt: 'Login expired, please login again'
|
||||||
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: 'Cancel'
|
cancel: 'Cancel'
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,6 +57,7 @@ export default {
|
||||||
application: 'Tooted with {{application}}'
|
application: 'Tooted with {{application}}'
|
||||||
},
|
},
|
||||||
conversation: {
|
conversation: {
|
||||||
|
withAccounts: 'With',
|
||||||
delete: {
|
delete: {
|
||||||
function: 'Delete direct message'
|
function: 'Delete direct message'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export default {
|
export default {
|
||||||
|
index: {
|
||||||
|
localCorrupt: '登录已过期,请重新登录'
|
||||||
|
},
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: '取消'
|
cancel: '取消'
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,6 +57,7 @@ export default {
|
||||||
application: '发自于 {{application}}'
|
application: '发自于 {{application}}'
|
||||||
},
|
},
|
||||||
conversation: {
|
conversation: {
|
||||||
|
withAccounts: '与',
|
||||||
delete: {
|
delete: {
|
||||||
function: '删除私信'
|
function: '删除私信'
|
||||||
}
|
}
|
||||||
|
@ -141,7 +142,7 @@ export default {
|
||||||
},
|
},
|
||||||
count: {
|
count: {
|
||||||
voters: '已投{{count}}人 • ',
|
voters: '已投{{count}}人 • ',
|
||||||
votes: '{{count}}票 • '
|
votes: '已投{{count}}票 • '
|
||||||
},
|
},
|
||||||
expiration: {
|
expiration: {
|
||||||
expired: '投票已结束',
|
expired: '投票已结束',
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { useNavigation } from '@react-navigation/native'
|
||||||
import TimelineEmpty from '@root/components/Timelines/Timeline/Empty'
|
import TimelineEmpty from '@root/components/Timelines/Timeline/Empty'
|
||||||
import { useListsQuery } from '@utils/queryHooks/lists'
|
import { useListsQuery } from '@utils/queryHooks/lists'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
|
||||||
|
|
||||||
const ScreenMeLists: React.FC = () => {
|
const ScreenMeLists: React.FC = () => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation()
|
||||||
|
@ -32,11 +31,4 @@ const ScreenMeLists: React.FC = () => {
|
||||||
return <>{children}</>
|
return <>{children}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
loading: {
|
|
||||||
flex: 1,
|
|
||||||
alignItems: 'center'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default ScreenMeLists
|
export default ScreenMeLists
|
||||||
|
|
|
@ -22,7 +22,7 @@ const MyInfo: React.FC<Props> = ({ setData }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AccountHeader account={data} limitHeight />
|
<AccountHeader account={data} limitHeight />
|
||||||
<AccountInformation account={data} ownAccount />
|
<AccountInformation account={data} myInfo />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
@ -129,7 +130,12 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
},
|
},
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
if (buttonIndex < options.length) {
|
if (buttonIndex < options.length) {
|
||||||
|
analytics('settings_language_press', {
|
||||||
|
current: i18n.language,
|
||||||
|
new: availableLanguages[buttonIndex]
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
|
// @ts-ignore
|
||||||
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
dispatch(changeLanguage(availableLanguages[buttonIndex]))
|
||||||
i18n.changeLanguage(availableLanguages[buttonIndex])
|
i18n.changeLanguage(availableLanguages[buttonIndex])
|
||||||
}
|
}
|
||||||
|
@ -156,15 +162,27 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
switch (buttonIndex) {
|
switch (buttonIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
|
analytics('settings_appearance_press', {
|
||||||
|
current: settingsTheme,
|
||||||
|
new: 'auto'
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeTheme('auto'))
|
dispatch(changeTheme('auto'))
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
|
analytics('settings_appearance_press', {
|
||||||
|
current: settingsTheme,
|
||||||
|
new: 'light'
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeTheme('light'))
|
dispatch(changeTheme('light'))
|
||||||
setTheme('light')
|
setTheme('light')
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
|
analytics('settings_appearance_press', {
|
||||||
|
current: settingsTheme,
|
||||||
|
new: 'dark'
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeTheme('dark'))
|
dispatch(changeTheme('dark'))
|
||||||
setTheme('dark')
|
setTheme('dark')
|
||||||
|
@ -192,10 +210,18 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
switch (buttonIndex) {
|
switch (buttonIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
|
analytics('settings_browser_press', {
|
||||||
|
current: settingsBrowser,
|
||||||
|
new: 'internal'
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeBrowser('internal'))
|
dispatch(changeBrowser('internal'))
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
|
analytics('settings_browser_press', {
|
||||||
|
current: settingsBrowser,
|
||||||
|
new: 'external'
|
||||||
|
})
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
dispatch(changeBrowser('external'))
|
dispatch(changeBrowser('external'))
|
||||||
break
|
break
|
||||||
|
@ -220,6 +246,9 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
}
|
}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
|
analytics('settings_cache_press', {
|
||||||
|
size: cacheSize ? prettyBytes(cacheSize) : 'empty'
|
||||||
|
})
|
||||||
await CacheManager.clearCache()
|
await CacheManager.clearCache()
|
||||||
haptics('Success')
|
haptics('Success')
|
||||||
setCacheSize(0)
|
setCacheSize(0)
|
||||||
|
@ -237,7 +266,10 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() => Linking.openURL('https://www.patreon.com/xmflsct')}
|
onPress={() => {
|
||||||
|
analytics('settings_support_press')
|
||||||
|
Linking.openURL('https://www.patreon.com/xmflsct')
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<MenuRow
|
<MenuRow
|
||||||
title={t('content.review.heading')}
|
title={t('content.review.heading')}
|
||||||
|
@ -249,11 +281,12 @@ const ScreenMeSettings: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('settings_review_press')
|
||||||
StoreReview.isAvailableAsync().then(() =>
|
StoreReview.isAvailableAsync().then(() =>
|
||||||
StoreReview.requestReview()
|
StoreReview.requestReview()
|
||||||
)
|
)
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</MenuContainer>
|
</MenuContainer>
|
||||||
<MenuContainer>
|
<MenuContainer>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
import { HeaderCenter, HeaderLeft } from '@components/Header'
|
||||||
|
import { StackScreenProps } from '@react-navigation/stack'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Platform, StyleSheet } from 'react-native'
|
import { Platform, StyleSheet } from 'react-native'
|
||||||
|
@ -7,7 +8,10 @@ import ScreenMeSwitchRoot from './Switch/Root'
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator()
|
const Stack = createNativeStackNavigator()
|
||||||
|
|
||||||
const ScreenMeSwitch: React.FC = ({ navigation }) => {
|
const ScreenMeSwitch: React.FC<StackScreenProps<
|
||||||
|
Nav.MeStackParamList,
|
||||||
|
'Screen-Me-Switch'
|
||||||
|
>> = ({ navigation }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
return (
|
return (
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import ComponentInstance from '@components/Instance'
|
import ComponentInstance from '@components/Instance'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
@ -47,6 +48,7 @@ const AccountButton: React.FC<Props> = ({
|
||||||
disabled ? ' ✓' : ''
|
disabled ? ' ✓' : ''
|
||||||
}`}
|
}`}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('switch_existing_press')
|
||||||
dispatch(localUpdateActiveIndex(index))
|
dispatch(localUpdateActiveIndex(index))
|
||||||
queryClient.clear()
|
queryClient.clear()
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
|
|
|
@ -34,7 +34,7 @@ const ScreenNotifications: React.FC = () => {
|
||||||
}
|
}
|
||||||
</Stack.Screen>
|
</Stack.Screen>
|
||||||
|
|
||||||
{sharedScreens(Stack)}
|
{sharedScreens(Stack as any)}
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import BottomSheet from '@components/BottomSheet'
|
import BottomSheet from '@components/BottomSheet'
|
||||||
import { HeaderRight } from '@components/Header'
|
import { HeaderRight } from '@components/Header'
|
||||||
import Timeline from '@components/Timelines/Timeline'
|
import Timeline from '@components/Timelines/Timeline'
|
||||||
|
@ -49,7 +50,12 @@ const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderRight
|
<HeaderRight
|
||||||
content='MoreHorizontal'
|
content='MoreHorizontal'
|
||||||
onPress={() => setBottomSheetVisible(true)}
|
onPress={() => {
|
||||||
|
analytics('bottomsheet_open_press', {
|
||||||
|
page: 'account'
|
||||||
|
})
|
||||||
|
setBottomSheetVisible(true)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
|
@ -22,7 +24,9 @@ export interface Props {
|
||||||
|
|
||||||
const AccountAttachments = React.memo(
|
const AccountAttachments = React.memo(
|
||||||
({ account }: Props) => {
|
({ account }: Props) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
|
|
||||||
const width =
|
const width =
|
||||||
|
@ -58,9 +62,11 @@ const AccountAttachments = React.memo(
|
||||||
if (index === 3) {
|
if (index === 3) {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
navigation.push('Screen-Shared-Attachments', { account })
|
analytics('account_attachment_more_press')
|
||||||
}
|
account &&
|
||||||
|
navigation.push('Screen-Shared-Attachments', { account })
|
||||||
|
}}
|
||||||
children={
|
children={
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
@ -92,9 +98,10 @@ const AccountAttachments = React.memo(
|
||||||
blurhash={item.media_attachments[0].blurhash}
|
blurhash={item.media_attachments[0].blurhash}
|
||||||
dimension={{ width: width, height: width }}
|
dimension={{ width: width, height: width }}
|
||||||
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
style={{ marginLeft: StyleConstants.Spacing.Global.PagePadding }}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('account_attachment_item_press')
|
||||||
navigation.push('Screen-Shared-Toot', { toot: item })
|
navigation.push('Screen-Shared-Toot', { toot: item })
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
import { Placeholder, Fade } from 'rn-placeholder'
|
import { Placeholder, Fade } from 'rn-placeholder'
|
||||||
import AccountInformationAccount from './Information/Account'
|
import AccountInformationAccount from './Information/Account'
|
||||||
import AccountInformationActions from './Information/Actions'
|
import AccountInformationActions from './Information/Actions'
|
||||||
|
@ -15,13 +17,11 @@ import AccountInformationSwitch from './Information/Switch'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
ownAccount?: boolean
|
myInfo?: boolean // Showing from my info page
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformation: React.FC<Props> = ({
|
const AccountInformation: React.FC<Props> = ({ account, myInfo = false }) => {
|
||||||
account,
|
const ownAccount = account?.id === useSelector(getLocalAccount)?.id
|
||||||
ownAccount = false
|
|
||||||
}) => {
|
|
||||||
const { mode, theme } = useTheme()
|
const { mode, theme } = useTheme()
|
||||||
|
|
||||||
const animation = useCallback(
|
const animation = useCallback(
|
||||||
|
@ -35,21 +35,24 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
<View style={styles.base}>
|
<View style={styles.base}>
|
||||||
<Placeholder Animation={animation}>
|
<Placeholder Animation={animation}>
|
||||||
<View style={styles.avatarAndActions}>
|
<View style={styles.avatarAndActions}>
|
||||||
<AccountInformationAvatar account={account} />
|
<AccountInformationAvatar account={account} myInfo={myInfo} />
|
||||||
<View style={styles.actions}>
|
<View style={styles.actions}>
|
||||||
{ownAccount ? (
|
{myInfo ? (
|
||||||
<AccountInformationSwitch />
|
<AccountInformationSwitch />
|
||||||
) : (
|
) : (
|
||||||
<AccountInformationActions account={account} />
|
<AccountInformationActions
|
||||||
|
account={account}
|
||||||
|
ownAccount={ownAccount}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<AccountInformationName account={account} />
|
<AccountInformationName account={account} />
|
||||||
|
|
||||||
<AccountInformationAccount account={account} ownAccount={ownAccount} />
|
<AccountInformationAccount account={account} myInfo={myInfo} />
|
||||||
|
|
||||||
{!ownAccount ? (
|
{!myInfo ? (
|
||||||
<>
|
<>
|
||||||
{account?.fields && account.fields.length > 0 ? (
|
{account?.fields && account.fields.length > 0 ? (
|
||||||
<AccountInformationFields account={account} />
|
<AccountInformationFields account={account} />
|
||||||
|
@ -64,7 +67,7 @@ const AccountInformation: React.FC<Props> = ({
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<AccountInformationStats account={account} ownAccount={ownAccount} />
|
<AccountInformationStats account={account} myInfo={myInfo} />
|
||||||
</Placeholder>
|
</Placeholder>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,13 +9,10 @@ import { PlaceholderLine } from 'rn-placeholder'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
ownAccount?: boolean
|
myInfo: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformationAccount: React.FC<Props> = ({
|
const AccountInformationAccount: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
account,
|
|
||||||
ownAccount
|
|
||||||
}) => {
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const localAccount = useSelector(getLocalAccount)
|
const localAccount = useSelector(getLocalAccount)
|
||||||
const localUri = useSelector(getLocalUri)
|
const localUri = useSelector(getLocalUri)
|
||||||
|
@ -45,7 +42,7 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
}, [account?.moved])
|
}, [account?.moved])
|
||||||
|
|
||||||
if (account || (ownAccount && localAccount !== undefined)) {
|
if (account || (myInfo && localAccount !== undefined)) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={[styles.base, { flexDirection: 'row', alignItems: 'center' }]}
|
style={[styles.base, { flexDirection: 'row', alignItems: 'center' }]}
|
||||||
|
@ -60,8 +57,8 @@ const AccountInformationAccount: React.FC<Props> = ({
|
||||||
]}
|
]}
|
||||||
selectable
|
selectable
|
||||||
>
|
>
|
||||||
@{ownAccount ? localAccount?.acct : account?.acct}
|
@{myInfo ? localAccount?.acct : account?.acct}
|
||||||
{ownAccount ? `@${localUri}` : null}
|
{myInfo ? `@${localUri}` : null}
|
||||||
</Text>
|
</Text>
|
||||||
{movedContent}
|
{movedContent}
|
||||||
{account?.locked ? (
|
{account?.locked ? (
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import { RelationshipOutgoing } from '@components/Relationship'
|
import { RelationshipOutgoing } from '@components/Relationship'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -9,22 +11,25 @@ import { StyleSheet } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
|
ownAccount: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const GoToMoved = ({ account }: { account: Mastodon.Account }) => {
|
const GoToMoved = ({ accountMoved }: { accountMoved: Mastodon.Account }) => {
|
||||||
const { t } = useTranslation('sharedAccount')
|
const { t } = useTranslation('sharedAccount')
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
const query = useRelationshipQuery({ id: account.id })
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
|
|
||||||
return query.data && !query.data.blocked_by ? (
|
return (
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={t('content.moved')}
|
content={t('content.moved')}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
navigation.push('Screen-Shared-Account', { account: account.moved })
|
analytics('account_gotomoved_press')
|
||||||
}
|
navigation.push('Screen-Shared-Account', { account: accountMoved })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : null
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
||||||
|
@ -37,26 +42,30 @@ const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
||||||
type='icon'
|
type='icon'
|
||||||
content='Mail'
|
content='Mail'
|
||||||
style={styles.actionConversation}
|
style={styles.actionConversation}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('account_DM_press')
|
||||||
navigation.navigate('Screen-Shared-Compose', {
|
navigation.navigate('Screen-Shared-Compose', {
|
||||||
type: 'conversation',
|
type: 'conversation',
|
||||||
incomingStatus: { account }
|
accts: [account.acct]
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformationActions: React.FC<Props> = ({ account }) => {
|
const AccountInformationActions: React.FC<Props> = ({
|
||||||
|
account,
|
||||||
|
ownAccount
|
||||||
|
}) => {
|
||||||
return account && account.id ? (
|
return account && account.id ? (
|
||||||
account.moved ? (
|
account.moved ? (
|
||||||
<GoToMoved account={account} />
|
<GoToMoved accountMoved={account.moved} />
|
||||||
) : (
|
) : !ownAccount ? (
|
||||||
<>
|
<>
|
||||||
<Conversation account={account} />
|
<Conversation account={account} />
|
||||||
<RelationshipOutgoing id={account.id} />
|
<RelationshipOutgoing id={account.id} />
|
||||||
</>
|
</>
|
||||||
)
|
) : null
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { StyleSheet } from 'react-native'
|
import { Pressable, StyleSheet } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
|
myInfo: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformationAvatar: React.FC<Props> = ({ account }) => {
|
const AccountInformationAvatar: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const dimension = useMemo(
|
const dimension = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
width: StyleConstants.Avatar.L,
|
width: StyleConstants.Avatar.L,
|
||||||
|
@ -17,11 +24,21 @@ const AccountInformationAvatar: React.FC<Props> = ({ account }) => {
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GracefullyImage
|
<Pressable
|
||||||
style={styles.base}
|
disabled={!myInfo}
|
||||||
uri={{ original: account?.avatar }}
|
onPress={() => {
|
||||||
dimension={dimension}
|
analytics('account_avatar_press')
|
||||||
/>
|
myInfo &&
|
||||||
|
account &&
|
||||||
|
navigation.push('Screen-Shared-Account', { account })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<GracefullyImage
|
||||||
|
style={styles.base}
|
||||||
|
uri={{ original: account?.avatar }}
|
||||||
|
dimension={dimension}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { StyleConstants } from '@root/utils/styles/constants'
|
import { StyleConstants } from '@root/utils/styles/constants'
|
||||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
@ -8,11 +10,13 @@ import { PlaceholderLine } from 'rn-placeholder'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
account: Mastodon.Account | undefined
|
account: Mastodon.Account | undefined
|
||||||
ownAccount?: boolean
|
myInfo: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountInformationStats: React.FC<Props> = ({ account, ownAccount }) => {
|
const AccountInformationStats: React.FC<Props> = ({ account, myInfo }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation('sharedAccount')
|
const { t } = useTranslation('sharedAccount')
|
||||||
|
|
||||||
|
@ -24,9 +28,12 @@ const AccountInformationStats: React.FC<Props> = ({ account, ownAccount }) => {
|
||||||
children={t('content.summary.statuses_count', {
|
children={t('content.summary.statuses_count', {
|
||||||
count: account?.statuses_count || 0
|
count: account?.statuses_count || 0
|
||||||
})}
|
})}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
ownAccount && navigation.push('Screen-Shared-Account', { account })
|
analytics('account_stats_toots_press', {
|
||||||
}
|
count: account.statuses_count
|
||||||
|
})
|
||||||
|
myInfo && navigation.push('Screen-Shared-Account', { account })
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PlaceholderLine
|
<PlaceholderLine
|
||||||
|
@ -43,12 +50,15 @@ const AccountInformationStats: React.FC<Props> = ({ account, ownAccount }) => {
|
||||||
children={t('content.summary.following_count', {
|
children={t('content.summary.following_count', {
|
||||||
count: account?.following_count || 0
|
count: account?.following_count || 0
|
||||||
})}
|
})}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('account_stats_following_press', {
|
||||||
|
count: account.following_count
|
||||||
|
})
|
||||||
navigation.push('Screen-Shared-Relationships', {
|
navigation.push('Screen-Shared-Relationships', {
|
||||||
account,
|
account,
|
||||||
initialType: 'following'
|
initialType: 'following'
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PlaceholderLine
|
<PlaceholderLine
|
||||||
|
@ -65,12 +75,15 @@ const AccountInformationStats: React.FC<Props> = ({ account, ownAccount }) => {
|
||||||
children={t('content.summary.followers_count', {
|
children={t('content.summary.followers_count', {
|
||||||
count: account?.followers_count || 0
|
count: account?.followers_count || 0
|
||||||
})}
|
})}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('account_stats_followers_press', {
|
||||||
|
count: account.followers_count
|
||||||
|
})
|
||||||
navigation.push('Screen-Shared-Relationships', {
|
navigation.push('Screen-Shared-Relationships', {
|
||||||
account,
|
account,
|
||||||
initialType: 'followers'
|
initialType: 'followers'
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<PlaceholderLine
|
<PlaceholderLine
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { ParseHTML } from '@components/Parse'
|
import { ParseHTML } from '@components/Parse'
|
||||||
|
@ -108,14 +109,17 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||||
: theme.background
|
: theme.background
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('accnouncement_reaction_press', {
|
||||||
|
current: reaction.me
|
||||||
|
})
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
type: 'reaction',
|
type: 'reaction',
|
||||||
name: reaction.name,
|
name: reaction.name,
|
||||||
me: reaction.me
|
me: reaction.me
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
{reaction.url ? (
|
{reaction.url ? (
|
||||||
<Image
|
<Image
|
||||||
|
@ -153,13 +157,14 @@ const ScreenSharedAnnouncements: React.FC<SharedAnnouncementsProp> = ({
|
||||||
}
|
}
|
||||||
loading={mutation.isLoading}
|
loading={mutation.isLoading}
|
||||||
disabled={item.read}
|
disabled={item.read}
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('accnouncement_read_press')
|
||||||
!item.read &&
|
!item.read &&
|
||||||
mutation.mutate({
|
mutation.mutate({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
type: 'dismiss'
|
type: 'dismiss'
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import haptics from '@root/components/haptics'
|
import haptics from '@root/components/haptics'
|
||||||
import { store } from '@root/store'
|
import { store } from '@root/store'
|
||||||
|
@ -93,20 +94,11 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'reply':
|
case 'reply':
|
||||||
const actualStatus =
|
|
||||||
params.incomingStatus.reblog || params.incomingStatus
|
|
||||||
formatText({
|
|
||||||
textInput: 'text',
|
|
||||||
composeDispatch,
|
|
||||||
content: `@${actualStatus.account.acct} `,
|
|
||||||
disableDebounce: true
|
|
||||||
})
|
|
||||||
break
|
|
||||||
case 'conversation':
|
case 'conversation':
|
||||||
formatText({
|
formatText({
|
||||||
textInput: 'text',
|
textInput: 'text',
|
||||||
composeDispatch,
|
composeDispatch,
|
||||||
content: `@${params.incomingStatus.account.acct} `,
|
content: params.accts.map(acct => `@${acct}`).join(' ') + ' ',
|
||||||
disableDebounce: true
|
disableDebounce: true
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
|
@ -123,23 +115,32 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||||
type='text'
|
type='text'
|
||||||
content={t('heading.left.button')}
|
content={t('heading.left.button')}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('compose_header_back_press')
|
||||||
if (
|
if (
|
||||||
totalTextCount === 0 &&
|
totalTextCount === 0 &&
|
||||||
composeState.attachments.uploads.length === 0 &&
|
composeState.attachments.uploads.length === 0 &&
|
||||||
composeState.poll.active === false
|
composeState.poll.active === false
|
||||||
) {
|
) {
|
||||||
|
analytics('compose_header_back_empty')
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
analytics('compose_header_back_state_occupied')
|
||||||
Alert.alert(t('heading.left.alert.title'), undefined, [
|
Alert.alert(t('heading.left.alert.title'), undefined, [
|
||||||
{
|
{
|
||||||
text: t('heading.left.alert.buttons.exit'),
|
text: t('heading.left.alert.buttons.exit'),
|
||||||
style: 'destructive',
|
style: 'destructive',
|
||||||
onPress: () => navigation.goBack()
|
onPress: () => {
|
||||||
|
analytics('compose_header_back_occupied_confirm')
|
||||||
|
navigation.goBack()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: t('heading.left.alert.buttons.continue'),
|
text: t('heading.left.alert.buttons.continue'),
|
||||||
style: 'cancel'
|
style: 'cancel',
|
||||||
|
onPress: () => {
|
||||||
|
analytics('compose_header_back_occupied_cancel')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -174,6 +175,7 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||||
: t('heading.right.button.default')
|
: t('heading.right.button.default')
|
||||||
}
|
}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('compose_header_post_press')
|
||||||
composeDispatch({ type: 'posting', payload: true })
|
composeDispatch({ type: 'posting', payload: true })
|
||||||
|
|
||||||
composePost(params, composeState)
|
composePost(params, composeState)
|
||||||
|
@ -186,13 +188,18 @@ const Compose: React.FC<SharedComposeProp> = ({
|
||||||
]
|
]
|
||||||
queryClient.invalidateQueries(queryKey)
|
queryClient.invalidateQueries(queryKey)
|
||||||
|
|
||||||
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
|
switch (params?.type) {
|
||||||
queryClient.invalidateQueries(params.queryKey)
|
case 'edit':
|
||||||
|
case 'reply':
|
||||||
|
if (params?.queryKey && params.queryKey[1].page === 'Toot') {
|
||||||
|
queryClient.invalidateQueries(params.queryKey)
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// Sentry.Native.captureException(error)
|
Sentry.Native.captureException(error)
|
||||||
haptics('Error')
|
haptics('Error')
|
||||||
composeDispatch({ type: 'posting', payload: false })
|
composeDispatch({ type: 'posting', payload: false })
|
||||||
Alert.alert(t('heading.right.alert.title'), undefined, [
|
Alert.alert(t('heading.right.alert.title'), undefined, [
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import client from '@api/client'
|
import client from '@api/client'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import React, {
|
import React, {
|
||||||
|
@ -87,6 +88,7 @@ const ComposeEditAttachment: React.FC<Props> = ({
|
||||||
content={t('content.editAttachment.header.right.button')}
|
content={t('content.editAttachment.header.right.button')}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('editattachment_confirm_press')
|
||||||
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
|
if (!altText && focus.current.x === 0 && focus.current.y === 0) {
|
||||||
navigation.goBack()
|
navigation.goBack()
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { useActionSheet } from '@expo/react-native-action-sheet'
|
import { useActionSheet } from '@expo/react-native-action-sheet'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
|
@ -28,6 +29,9 @@ const ComposeActions: React.FC = () => {
|
||||||
if (composeState.poll.active) return
|
if (composeState.poll.active) return
|
||||||
|
|
||||||
if (composeState.attachments.uploads.length < 4) {
|
if (composeState.attachments.uploads.length < 4) {
|
||||||
|
analytics('compose_actions_attachment_press', {
|
||||||
|
count: composeState.attachments.uploads.length
|
||||||
|
})
|
||||||
return await addAttachment({
|
return await addAttachment({
|
||||||
composeDispatch,
|
composeDispatch,
|
||||||
showActionSheetWithOptions
|
showActionSheetWithOptions
|
||||||
|
@ -46,6 +50,9 @@ const ComposeActions: React.FC = () => {
|
||||||
}, [composeState.poll.active, composeState.attachments.uploads])
|
}, [composeState.poll.active, composeState.attachments.uploads])
|
||||||
const pollOnPress = useCallback(() => {
|
const pollOnPress = useCallback(() => {
|
||||||
if (!composeState.attachments.uploads.length) {
|
if (!composeState.attachments.uploads.length) {
|
||||||
|
analytics('compose_actions_poll_press', {
|
||||||
|
current: composeState.poll.active
|
||||||
|
})
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
|
@ -86,24 +93,43 @@ const ComposeActions: React.FC = () => {
|
||||||
buttonIndex => {
|
buttonIndex => {
|
||||||
switch (buttonIndex) {
|
switch (buttonIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
|
analytics('compose_actions_visibility_press', {
|
||||||
|
current: composeState.visibility,
|
||||||
|
new: 'public'
|
||||||
|
})
|
||||||
composeDispatch({ type: 'visibility', payload: 'public' })
|
composeDispatch({ type: 'visibility', payload: 'public' })
|
||||||
break
|
break
|
||||||
case 1:
|
case 1:
|
||||||
|
analytics('compose_actions_visibility_press', {
|
||||||
|
current: composeState.visibility,
|
||||||
|
new: 'unlisted'
|
||||||
|
})
|
||||||
composeDispatch({ type: 'visibility', payload: 'unlisted' })
|
composeDispatch({ type: 'visibility', payload: 'unlisted' })
|
||||||
break
|
break
|
||||||
case 2:
|
case 2:
|
||||||
|
analytics('compose_actions_visibility_press', {
|
||||||
|
current: composeState.visibility,
|
||||||
|
new: 'private'
|
||||||
|
})
|
||||||
composeDispatch({ type: 'visibility', payload: 'private' })
|
composeDispatch({ type: 'visibility', payload: 'private' })
|
||||||
break
|
break
|
||||||
case 3:
|
case 3:
|
||||||
|
analytics('compose_actions_visibility_press', {
|
||||||
|
current: composeState.visibility,
|
||||||
|
new: 'direct'
|
||||||
|
})
|
||||||
composeDispatch({ type: 'visibility', payload: 'direct' })
|
composeDispatch({ type: 'visibility', payload: 'direct' })
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, [])
|
}, [composeState.visibility])
|
||||||
|
|
||||||
const spoilerOnPress = useCallback(() => {
|
const spoilerOnPress = useCallback(() => {
|
||||||
|
analytics('compose_actions_spoiler_press', {
|
||||||
|
current: composeState.spoiler.active
|
||||||
|
})
|
||||||
if (composeState.spoiler.active) {
|
if (composeState.spoiler.active) {
|
||||||
composeState.textInputFocus.refs.text.current?.focus()
|
composeState.textInputFocus.refs.text.current?.focus()
|
||||||
}
|
}
|
||||||
|
@ -124,20 +150,15 @@ const ComposeActions: React.FC = () => {
|
||||||
}
|
}
|
||||||
}, [composeState.emoji.active, composeState.emoji.emojis])
|
}, [composeState.emoji.active, composeState.emoji.emojis])
|
||||||
const emojiOnPress = useCallback(() => {
|
const emojiOnPress = useCallback(() => {
|
||||||
|
analytics('compose_actions_emojis_press', {
|
||||||
|
current: composeState.emoji.active
|
||||||
|
})
|
||||||
if (composeState.emoji.emojis) {
|
if (composeState.emoji.emojis) {
|
||||||
if (composeState.emoji.active) {
|
layoutAnimation()
|
||||||
layoutAnimation()
|
composeDispatch({
|
||||||
composeDispatch({
|
type: 'emoji',
|
||||||
type: 'emoji',
|
payload: { ...composeState.emoji, active: !composeState.emoji.active }
|
||||||
payload: { ...composeState.emoji, active: false }
|
})
|
||||||
})
|
|
||||||
} else {
|
|
||||||
layoutAnimation()
|
|
||||||
composeDispatch({
|
|
||||||
type: 'emoji',
|
|
||||||
payload: { ...composeState.emoji, active: true }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [composeState.emoji.active, composeState.emoji.emojis])
|
}, [composeState.emoji.active, composeState.emoji.emojis])
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
|
@ -39,14 +40,15 @@ const ComposeAttachments: React.FC = () => {
|
||||||
const flatListRef = useRef<FlatList>(null)
|
const flatListRef = useRef<FlatList>(null)
|
||||||
let prevOffsets = useRef<number[]>()
|
let prevOffsets = useRef<number[]>()
|
||||||
|
|
||||||
const sensitiveOnPress = useCallback(
|
const sensitiveOnPress = useCallback(() => {
|
||||||
() =>
|
analytics('compose_attachment_sensitive_press', {
|
||||||
composeDispatch({
|
current: composeState.attachments.sensitive
|
||||||
type: 'attachments/sensitive',
|
})
|
||||||
payload: { sensitive: !composeState.attachments.sensitive }
|
composeDispatch({
|
||||||
}),
|
type: 'attachments/sensitive',
|
||||||
[composeState.attachments.sensitive]
|
payload: { sensitive: !composeState.attachments.sensitive }
|
||||||
)
|
})
|
||||||
|
}, [composeState.attachments.sensitive])
|
||||||
|
|
||||||
const calculateWidth = useCallback(item => {
|
const calculateWidth = useCallback(item => {
|
||||||
if (item.local) {
|
if (item.local) {
|
||||||
|
@ -158,6 +160,7 @@ const ComposeAttachments: React.FC = () => {
|
||||||
round
|
round
|
||||||
overlay
|
overlay
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('compose_attachment_delete')
|
||||||
layoutAnimation()
|
layoutAnimation()
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'attachment/delete',
|
type: 'attachment/delete',
|
||||||
|
@ -172,11 +175,12 @@ const ComposeAttachments: React.FC = () => {
|
||||||
spacing='M'
|
spacing='M'
|
||||||
round
|
round
|
||||||
overlay
|
overlay
|
||||||
onPress={() =>
|
onPress={() => {
|
||||||
|
analytics('compose_attachment_edit')
|
||||||
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
|
||||||
index
|
index
|
||||||
})
|
})
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
@ -196,9 +200,10 @@ const ComposeAttachments: React.FC = () => {
|
||||||
backgroundColor: theme.backgroundOverlay
|
backgroundColor: theme.backgroundOverlay
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
onPress={async () =>
|
onPress={async () => {
|
||||||
|
analytics('compose_attachment_add_container_press')
|
||||||
await addAttachment({ composeDispatch, showActionSheetWithOptions })
|
await addAttachment({ composeDispatch, showActionSheetWithOptions })
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
type='icon'
|
type='icon'
|
||||||
|
@ -206,9 +211,10 @@ const ComposeAttachments: React.FC = () => {
|
||||||
spacing='M'
|
spacing='M'
|
||||||
round
|
round
|
||||||
overlay
|
overlay
|
||||||
onPress={async () =>
|
onPress={async () => {
|
||||||
|
analytics('compose_attachment_add_button_press')
|
||||||
await addAttachment({ composeDispatch, showActionSheetWithOptions })
|
await addAttachment({ composeDispatch, showActionSheetWithOptions })
|
||||||
}
|
}}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top:
|
top:
|
||||||
|
|
|
@ -12,10 +12,12 @@ import {
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import ComposeContext from '../../utils/createContext'
|
import ComposeContext from '../../utils/createContext'
|
||||||
import updateText from '../../updateText'
|
import updateText from '../../updateText'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
const SingleEmoji = ({ emoji }: { emoji: Mastodon.Emoji }) => {
|
||||||
const { composeState, composeDispatch } = useContext(ComposeContext)
|
const { composeState, composeDispatch } = useContext(ComposeContext)
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('compose_emoji_add')
|
||||||
updateText({
|
updateText({
|
||||||
composeState,
|
composeState,
|
||||||
composeDispatch,
|
composeDispatch,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import Button from '@components/Button'
|
import Button from '@components/Button'
|
||||||
import Icon from '@components/Icon'
|
import Icon from '@components/Icon'
|
||||||
import { MenuRow } from '@components/Menu'
|
import { MenuRow } from '@components/Menu'
|
||||||
|
@ -79,6 +80,7 @@ const ComposePoll: React.FC = () => {
|
||||||
<View style={styles.firstButton}>
|
<View style={styles.firstButton}>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('compose_poll_reduce_press')
|
||||||
total > 2 &&
|
total > 2 &&
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
|
@ -93,6 +95,7 @@ const ComposePoll: React.FC = () => {
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
|
analytics('compose_poll_increase_press')
|
||||||
total < 4 &&
|
total < 4 &&
|
||||||
composeDispatch({
|
composeDispatch({
|
||||||
type: 'poll',
|
type: 'poll',
|
||||||
|
@ -122,12 +125,18 @@ const ComposePoll: React.FC = () => {
|
||||||
],
|
],
|
||||||
cancelButtonIndex: 2
|
cancelButtonIndex: 2
|
||||||
},
|
},
|
||||||
index =>
|
index => {
|
||||||
index < 2 &&
|
if (index < 2) {
|
||||||
composeDispatch({
|
analytics('compose_poll_expiration_press', {
|
||||||
type: 'poll',
|
current: multiple,
|
||||||
payload: { multiple: index === 1 }
|
new: index === 1
|
||||||
})
|
})
|
||||||
|
composeDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { multiple: index === 1 }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
@ -155,12 +164,18 @@ const ComposePoll: React.FC = () => {
|
||||||
],
|
],
|
||||||
cancelButtonIndex: 7
|
cancelButtonIndex: 7
|
||||||
},
|
},
|
||||||
index =>
|
index => {
|
||||||
index < 7 &&
|
if (index < 7) {
|
||||||
composeDispatch({
|
analytics('compose_poll_expiration_press', {
|
||||||
type: 'poll',
|
current: expire,
|
||||||
payload: { expire: expirations[index] }
|
new: expirations[index]
|
||||||
})
|
})
|
||||||
|
composeDispatch({
|
||||||
|
type: 'poll',
|
||||||
|
payload: { expire: expirations[index] }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
iconBack='ChevronRight'
|
iconBack='ChevronRight'
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { Alert, Linking } from 'react-native'
|
||||||
import { ComposeAction } from '../../utils/types'
|
import { ComposeAction } from '../../utils/types'
|
||||||
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
import { ActionSheetOptions } from '@expo/react-native-action-sheet'
|
||||||
import i18next from 'i18next'
|
import i18next from 'i18next'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
|
@ -160,14 +161,23 @@ const addAttachment = async ({
|
||||||
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.cancel'
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.cancel'
|
||||||
),
|
),
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
onPress: () => {}
|
onPress: () => {
|
||||||
|
analytics('compose_addattachment_medialibrary_nopermission', {
|
||||||
|
action: 'cancel'
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18next.t(
|
text: i18next.t(
|
||||||
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.settings'
|
'sharedCompose:content.root.actions.attachment.actions.library.alert.buttons.settings'
|
||||||
),
|
),
|
||||||
style: 'default',
|
style: 'default',
|
||||||
onPress: () => Linking.openURL('app-settings:')
|
onPress: () => {
|
||||||
|
analytics('compose_addattachment_medialibrary_nopermission', {
|
||||||
|
action: 'settings'
|
||||||
|
})
|
||||||
|
Linking.openURL('app-settings:')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -197,14 +207,23 @@ const addAttachment = async ({
|
||||||
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.cancel'
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.cancel'
|
||||||
),
|
),
|
||||||
style: 'cancel',
|
style: 'cancel',
|
||||||
onPress: () => {}
|
onPress: () => {
|
||||||
|
analytics('compose_addattachment_camera_nopermission', {
|
||||||
|
action: 'cancel'
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: i18next.t(
|
text: i18next.t(
|
||||||
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.settings'
|
'sharedCompose:content.root.actions.attachment.actions.photo.alert.buttons.settings'
|
||||||
),
|
),
|
||||||
style: 'default',
|
style: 'default',
|
||||||
onPress: () => Linking.openURL('app-settings:')
|
onPress: () => {
|
||||||
|
analytics('compose_addattachment_camera_nopermission', {
|
||||||
|
action: 'settings'
|
||||||
|
})
|
||||||
|
Linking.openURL('app-settings:')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import React, { useContext } from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { StyleSheet, View } from 'react-native'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import ComposeContext from '../utils/createContext'
|
import ComposeContext from '../utils/createContext'
|
||||||
import PostingAs from './Header/PostingAs'
|
import ComposePostingAs from './Header/PostingAs'
|
||||||
import ComposeSpoilerInput from './Header/SpoilerInput'
|
import ComposeSpoilerInput from './Header/SpoilerInput'
|
||||||
import ComposeTextInput from './Header/TextInput'
|
import ComposeTextInput from './Header/TextInput'
|
||||||
|
|
||||||
|
@ -22,10 +22,7 @@ const ComposeRootHeader: React.FC = () => {
|
||||||
localInstances.length &&
|
localInstances.length &&
|
||||||
localInstances.length > 1 && (
|
localInstances.length > 1 && (
|
||||||
<View style={styles.postingAs}>
|
<View style={styles.postingAs}>
|
||||||
<PostingAs
|
<ComposePostingAs />
|
||||||
id={localInstances[localActiveIndex].account.id}
|
|
||||||
domain={localInstances[localActiveIndex].uri}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
{composeState.spoiler.active ? <ComposeSpoilerInput /> : null}
|
||||||
|
|
|
@ -1,39 +1,30 @@
|
||||||
import { useAccountQuery } from '@utils/queryHooks/account'
|
import { getLocalAccount, getLocalUri } from '@utils/slices/instancesSlice'
|
||||||
import { InstanceLocal } from '@utils/slices/instancesSlice'
|
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { StyleSheet, Text } from 'react-native'
|
import { StyleSheet, Text } from 'react-native'
|
||||||
import { Chase } from 'react-native-animated-spinkit'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
const ComposePostingAs: React.FC<{
|
const ComposePostingAs = React.memo(
|
||||||
id: Mastodon.Account['id']
|
() => {
|
||||||
domain: InstanceLocal['url']
|
const { t } = useTranslation('sharedCompose')
|
||||||
}> = ({ id, domain }) => {
|
const { theme } = useTheme()
|
||||||
const { t } = useTranslation('sharedCompose')
|
|
||||||
const { theme } = useTheme()
|
|
||||||
|
|
||||||
const { data, status } = useAccountQuery({ id })
|
const localAccount = useSelector(getLocalAccount)
|
||||||
|
const localUri = useSelector(getLocalUri)
|
||||||
|
|
||||||
switch (status) {
|
return (
|
||||||
case 'loading':
|
<Text style={[styles.text, { color: theme.secondary }]}>
|
||||||
return (
|
{t('content.root.header.postingAs', {
|
||||||
<Chase
|
acct: localAccount?.acct,
|
||||||
size={StyleConstants.Font.LineHeight.M - 2}
|
domain: localUri
|
||||||
color={theme.secondary}
|
})}
|
||||||
/>
|
</Text>
|
||||||
)
|
)
|
||||||
case 'success':
|
},
|
||||||
return (
|
() => true
|
||||||
<Text style={[styles.text, { color: theme.secondary }]}>
|
)
|
||||||
{t('content.root.header.postingAs', { acct: data?.acct, domain })}
|
|
||||||
</Text>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
text: {
|
text: {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import ComponentAccount from '@components/Account'
|
import ComponentAccount from '@components/Account'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import haptics from '@components/haptics'
|
import haptics from '@components/haptics'
|
||||||
import ComponentHashtag from '@components/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
import React, { Dispatch, useCallback } from 'react'
|
import React, { Dispatch, useCallback } from 'react'
|
||||||
|
@ -16,6 +17,9 @@ const ComposeRootSuggestion = React.memo(
|
||||||
composeDispatch: Dispatch<ComposeAction>
|
composeDispatch: Dispatch<ComposeAction>
|
||||||
}) => {
|
}) => {
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('compose_suggestion_press', {
|
||||||
|
type: item.acct ? 'account' : 'hashtag'
|
||||||
|
})
|
||||||
const focusedInput = composeState.textInputFocus.current
|
const focusedInput = composeState.textInputFocus.current
|
||||||
updateText({
|
updateText({
|
||||||
composeState: {
|
composeState: {
|
||||||
|
@ -36,9 +40,9 @@ const ComposeRootSuggestion = React.memo(
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return item.acct ? (
|
return item.acct ? (
|
||||||
<ComponentAccount account={item} onPress={onPress} />
|
<ComponentAccount account={item} onPress={onPress} origin='suggestion' />
|
||||||
) : (
|
) : (
|
||||||
<ComponentHashtag tag={item} onPress={onPress} />
|
<ComponentHashtag hashtag={item} onPress={onPress} origin='suggestion' />
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
() => true
|
() => true
|
||||||
|
|
|
@ -3,51 +3,50 @@ import { getLocalAccount } from '@utils/slices/instancesSlice'
|
||||||
import composeInitialState from './initialState'
|
import composeInitialState from './initialState'
|
||||||
import { ComposeState } from './types'
|
import { ComposeState } from './types'
|
||||||
|
|
||||||
export interface Props {
|
const composeParseState = (
|
||||||
type: 'reply' | 'conversation' | 'edit'
|
params: NonNullable<Nav.SharedStackParamList['Screen-Shared-Compose']>
|
||||||
incomingStatus: Mastodon.Status
|
): ComposeState => {
|
||||||
}
|
switch (params.type) {
|
||||||
|
|
||||||
const composeParseState = ({ type, incomingStatus }: Props): ComposeState => {
|
|
||||||
switch (type) {
|
|
||||||
case 'edit':
|
case 'edit':
|
||||||
return {
|
return {
|
||||||
...composeInitialState,
|
...composeInitialState,
|
||||||
...(incomingStatus.spoiler_text && {
|
...(params.incomingStatus.spoiler_text && {
|
||||||
spoiler: { ...composeInitialState.spoiler, active: true }
|
spoiler: { ...composeInitialState.spoiler, active: true }
|
||||||
}),
|
}),
|
||||||
...(incomingStatus.poll && {
|
...(params.incomingStatus.poll && {
|
||||||
poll: {
|
poll: {
|
||||||
active: true,
|
active: true,
|
||||||
total: incomingStatus.poll.options.length,
|
total: params.incomingStatus.poll.options.length,
|
||||||
options: {
|
options: {
|
||||||
'0': incomingStatus.poll.options[0]?.title || undefined,
|
'0': params.incomingStatus.poll.options[0]?.title || undefined,
|
||||||
'1': incomingStatus.poll.options[1]?.title || undefined,
|
'1': params.incomingStatus.poll.options[1]?.title || undefined,
|
||||||
'2': incomingStatus.poll.options[2]?.title || undefined,
|
'2': params.incomingStatus.poll.options[2]?.title || undefined,
|
||||||
'3': incomingStatus.poll.options[3]?.title || undefined
|
'3': params.incomingStatus.poll.options[3]?.title || undefined
|
||||||
},
|
},
|
||||||
multiple: incomingStatus.poll.multiple,
|
multiple: params.incomingStatus.poll.multiple,
|
||||||
expire: '86400' // !!!
|
expire: '86400' // !!!
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
...(incomingStatus.media_attachments && {
|
...(params.incomingStatus.media_attachments && {
|
||||||
attachments: {
|
attachments: {
|
||||||
sensitive: incomingStatus.sensitive,
|
sensitive: params.incomingStatus.sensitive,
|
||||||
uploads: incomingStatus.media_attachments.map(media => ({
|
uploads: params.incomingStatus.media_attachments.map(media => ({
|
||||||
remote: media
|
remote: media
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
visibility:
|
visibility:
|
||||||
incomingStatus.visibility ||
|
params.incomingStatus.visibility ||
|
||||||
getLocalAccount(store.getState())?.preferences[
|
getLocalAccount(store.getState())?.preferences[
|
||||||
'posting:default:visibility'
|
'posting:default:visibility'
|
||||||
] ||
|
] ||
|
||||||
'public',
|
'public',
|
||||||
...(incomingStatus.visibility === 'direct' && { visibilityLock: true })
|
...(params.incomingStatus.visibility === 'direct' && {
|
||||||
|
visibilityLock: true
|
||||||
|
})
|
||||||
}
|
}
|
||||||
case 'reply':
|
case 'reply':
|
||||||
const actualStatus = incomingStatus.reblog || incomingStatus
|
const actualStatus = params.incomingStatus.reblog || params.incomingStatus
|
||||||
return {
|
return {
|
||||||
...composeInitialState,
|
...composeInitialState,
|
||||||
visibility: actualStatus.visibility,
|
visibility: actualStatus.visibility,
|
||||||
|
@ -57,12 +56,6 @@ const composeParseState = ({ type, incomingStatus }: Props): ComposeState => {
|
||||||
case 'conversation':
|
case 'conversation':
|
||||||
return {
|
return {
|
||||||
...composeInitialState,
|
...composeInitialState,
|
||||||
text: {
|
|
||||||
count: incomingStatus.account.acct.length + 2,
|
|
||||||
raw: `@${incomingStatus.account.acct} `,
|
|
||||||
formatted: undefined,
|
|
||||||
selection: { start: 0, end: 0 }
|
|
||||||
},
|
|
||||||
visibility: 'direct',
|
visibility: 'direct',
|
||||||
visibilityLock: true
|
visibilityLock: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import { HeaderLeft, HeaderRight } from '@components/Header'
|
import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { findIndex } from 'lodash'
|
import { findIndex } from 'lodash'
|
||||||
import React, { useCallback, useState } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
import { Image, Platform, Share, StatusBar, StyleSheet, Text } from 'react-native'
|
import {
|
||||||
|
Image,
|
||||||
|
Platform,
|
||||||
|
Share,
|
||||||
|
StatusBar,
|
||||||
|
StyleSheet,
|
||||||
|
Text
|
||||||
|
} from 'react-native'
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer'
|
import ImageViewer from 'react-native-image-zoom-viewer'
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||||
|
@ -47,6 +55,7 @@ const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
const onPress = useCallback(() => {
|
const onPress = useCallback(() => {
|
||||||
|
analytics('imageviewer_share_press')
|
||||||
switch (Platform.OS) {
|
switch (Platform.OS) {
|
||||||
case 'ios':
|
case 'ios':
|
||||||
return Share.share({ url: imageUrls[currentIndex].url })
|
return Share.share({ url: imageUrls[currentIndex].url })
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ComponentSeparator from '@components/Separator'
|
||||||
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
import TimelineEmpty from '@components/Timelines/Timeline/Empty'
|
||||||
import TimelineEnd from '@components/Timelines/Timeline/End'
|
import TimelineEnd from '@components/Timelines/Timeline/End'
|
||||||
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
import { useNavigation, useScrollToTop } from '@react-navigation/native'
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
|
import { useRelationshipsQuery } from '@utils/queryHooks/relationships'
|
||||||
import React, { useCallback, useMemo, useRef } from 'react'
|
import React, { useCallback, useMemo, useRef } from 'react'
|
||||||
import { RefreshControl, StyleSheet } from 'react-native'
|
import { RefreshControl, StyleSheet } from 'react-native'
|
||||||
|
@ -14,7 +15,9 @@ export interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
||||||
const navigation = useNavigation()
|
const navigation = useNavigation<
|
||||||
|
StackNavigationProp<Nav.LocalStackParamList>
|
||||||
|
>()
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
data,
|
data,
|
||||||
|
@ -43,14 +46,7 @@ const RelationshipsList: React.FC<Props> = ({ id, type }) => {
|
||||||
|
|
||||||
const keyExtractor = useCallback(({ id }) => id, [])
|
const keyExtractor = useCallback(({ id }) => id, [])
|
||||||
const renderItem = useCallback(
|
const renderItem = useCallback(
|
||||||
({ item }) => (
|
({ item }) => <ComponentAccount account={item} origin='relationship' />,
|
||||||
<ComponentAccount
|
|
||||||
account={item}
|
|
||||||
onPress={() =>
|
|
||||||
navigation.push('Screen-Shared-Account', { account: item })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const flItemEmptyComponent = useMemo(
|
const flItemEmptyComponent = useMemo(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import ComponentAccount from '@components/Account'
|
import ComponentAccount from '@components/Account'
|
||||||
|
import analytics from '@components/analytics'
|
||||||
import ComponentHashtag from '@components/Hashtag'
|
import ComponentHashtag from '@components/Hashtag'
|
||||||
import ComponentSeparator from '@components/Separator'
|
import ComponentSeparator from '@components/Separator'
|
||||||
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
import TimelineDefault from '@components/Timelines/Timeline/Default'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
|
||||||
import { useSearchQuery } from '@utils/queryHooks/search'
|
import { useSearchQuery } from '@utils/queryHooks/search'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
|
@ -24,7 +24,6 @@ export interface Props {
|
||||||
|
|
||||||
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
||||||
const { t } = useTranslation('sharedSearch')
|
const { t } = useTranslation('sharedSearch')
|
||||||
const navigation = useNavigation()
|
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { status, data, refetch } = useSearchQuery({
|
const { status, data, refetch } = useSearchQuery({
|
||||||
term: searchTerm,
|
term: searchTerm,
|
||||||
|
@ -158,28 +157,11 @@ const ScreenSharedSearch: React.FC<Props> = ({ searchTerm }) => {
|
||||||
const listItem = useCallback(({ item, section }) => {
|
const listItem = useCallback(({ item, section }) => {
|
||||||
switch (section.title) {
|
switch (section.title) {
|
||||||
case 'accounts':
|
case 'accounts':
|
||||||
return (
|
return <ComponentAccount account={item} origin='search' />
|
||||||
<ComponentAccount
|
|
||||||
account={item}
|
|
||||||
onPress={() => {
|
|
||||||
navigation.push('Screen-Shared-Account', { account: item })
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'hashtags':
|
case 'hashtags':
|
||||||
return (
|
return <ComponentHashtag hashtag={item} origin='search' />
|
||||||
<ComponentHashtag
|
|
||||||
tag={item}
|
|
||||||
onPress={() => {
|
|
||||||
navigation.goBack()
|
|
||||||
navigation.push('Screen-Shared-Hashtag', {
|
|
||||||
hashtag: item.name
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'statuses':
|
case 'statuses':
|
||||||
return <TimelineDefault item={item} disableDetails />
|
return <TimelineDefault item={item} disableDetails origin='search' />
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ const netInfo = async (): Promise<{
|
||||||
|
|
||||||
if (netInfo.isConnected) {
|
if (netInfo.isConnected) {
|
||||||
log('log', 'netInfo', 'network connected')
|
log('log', 'netInfo', 'network connected')
|
||||||
if (activeIndex) {
|
if (activeIndex !== null) {
|
||||||
log('log', 'netInfo', 'checking locally stored credentials')
|
log('log', 'netInfo', 'checking locally stored credentials')
|
||||||
return client<Mastodon.Account>({
|
return client<Mastodon.Account>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
|
|
@ -5,6 +5,7 @@ import log from './log'
|
||||||
const sentry = () => {
|
const sentry = () => {
|
||||||
log('log', 'Sentry', 'initializing')
|
log('log', 'Sentry', 'initializing')
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
|
environment: Constants.manifest.extra.sentryEnv,
|
||||||
dsn: Constants.manifest.extra.sentryDSN,
|
dsn: Constants.manifest.extra.sentryDSN,
|
||||||
enableInExpoDevelopment: false,
|
enableInExpoDevelopment: false,
|
||||||
debug: __DEV__
|
debug: __DEV__
|
||||||
|
|
Loading…
Reference in New Issue