mirror of
https://github.com/tooot-app/app
synced 2025-03-10 16:40:07 +01:00
Updates
This commit is contained in:
parent
aa467f6911
commit
86231fb7b7
4
App.tsx
4
App.tsx
@ -1,7 +1,7 @@
|
||||
import { ActionSheetProvider } from '@expo/react-native-action-sheet'
|
||||
import Index from '@root/Index'
|
||||
import dev from '@root/startup/dev'
|
||||
// import sentry from '@root/startup/sentry'
|
||||
import sentry from '@root/startup/sentry'
|
||||
import log from '@root/startup/log'
|
||||
import audio from '@root/startup/audio'
|
||||
import onlineStatus from '@root/startup/onlineStatus'
|
||||
@ -22,7 +22,7 @@ if (Platform.OS === 'android') {
|
||||
|
||||
|
||||
dev()
|
||||
// sentry()
|
||||
sentry()
|
||||
audio()
|
||||
onlineStatus()
|
||||
|
||||
|
@ -62,7 +62,6 @@
|
||||
"react-native-reanimated": "2.0.0-rc.0",
|
||||
"react-native-safe-area-context": "3.1.9",
|
||||
"react-native-screens": "~2.15.0",
|
||||
"react-native-shimmer-placeholder": "^2.0.6",
|
||||
"react-native-svg": "12.1.0",
|
||||
"react-native-tab-view": "^2.15.2",
|
||||
"react-native-tab-view-viewpager-adapter": "^1.1.0",
|
||||
@ -72,6 +71,7 @@
|
||||
"react-redux": "^7.2.2",
|
||||
"react-timeago": "^5.2.0",
|
||||
"redux-persist": "^6.0.0",
|
||||
"rn-placeholder": "^3.0.3",
|
||||
"sentry-expo": "^3.0.4",
|
||||
"tslib": "^2.0.3"
|
||||
},
|
||||
|
2
src/@types/mastodon.d.ts
vendored
2
src/@types/mastodon.d.ts
vendored
@ -25,7 +25,7 @@ declare namespace Mastodon {
|
||||
following_count: number
|
||||
|
||||
// Others
|
||||
moved?: Status
|
||||
moved?: Account
|
||||
fields: Field[]
|
||||
bot: boolean
|
||||
source: Source
|
||||
|
8
src/@types/react-navigation.d.ts
vendored
8
src/@types/react-navigation.d.ts
vendored
@ -33,7 +33,13 @@ declare namespace Nav {
|
||||
hashtag: Mastodon.Tag['name']
|
||||
}
|
||||
'Screen-Shared-ImagesViewer': {
|
||||
imageUrls: (IImageInfo & {
|
||||
imageUrls: ({
|
||||
url: string
|
||||
width?: number
|
||||
height?: number
|
||||
originUrl?: string
|
||||
props?: any
|
||||
} & {
|
||||
preview_url: Mastodon.AttachmentImage['preview_url']
|
||||
remote_url: Mastodon.AttachmentImage['remote_url']
|
||||
imageIndex: number
|
||||
|
@ -17,6 +17,7 @@ import ScreenNotifications from '@screens/Notifications'
|
||||
import ScreenPublic from '@screens/Public'
|
||||
import { useTimelineQuery } from '@utils/queryHooks/timeline'
|
||||
import {
|
||||
getLocalAccount,
|
||||
getLocalActiveIndex,
|
||||
getLocalNotification,
|
||||
localUpdateAccountPreferences,
|
||||
@ -32,7 +33,7 @@ import React, {
|
||||
useMemo,
|
||||
useRef
|
||||
} from 'react'
|
||||
import { Platform, StatusBar } from 'react-native'
|
||||
import { Image, Platform, StatusBar } from 'react-native'
|
||||
import Toast from 'react-native-toast-message'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
@ -120,7 +121,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
if (!prevNotification || !prevNotification.latestTime) {
|
||||
dispatch(
|
||||
localUpdateNotification({
|
||||
unread: true
|
||||
unread: false
|
||||
})
|
||||
)
|
||||
} else if (
|
||||
@ -160,6 +161,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
|
||||
routeNameRef.current = currentRouteName
|
||||
}, [])
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const tabNavigatorScreenOptions = useCallback(
|
||||
({ route }): BottomTabNavigationOptions => ({
|
||||
tabBarIcon: ({
|
||||
@ -171,32 +173,43 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
color: string
|
||||
size: number
|
||||
}) => {
|
||||
let name: any
|
||||
let updateColor: string = color
|
||||
console.log()
|
||||
switch (route.name) {
|
||||
case 'Screen-Local':
|
||||
name = 'Home'
|
||||
break
|
||||
return <Icon name='Home' size={size} color={color} />
|
||||
case 'Screen-Public':
|
||||
name = 'Globe'
|
||||
!focused && (updateColor = theme.secondary)
|
||||
break
|
||||
return (
|
||||
<Icon
|
||||
name='Globe'
|
||||
size={size}
|
||||
color={!focused ? theme.secondary : color}
|
||||
/>
|
||||
)
|
||||
case 'Screen-Post':
|
||||
name = 'Plus'
|
||||
break
|
||||
return <Icon name='Plus' size={size} color={color} />
|
||||
case 'Screen-Notifications':
|
||||
name = 'Bell'
|
||||
break
|
||||
return <Icon name='Bell' size={size} color={color} />
|
||||
case 'Screen-Me':
|
||||
name = focused ? 'Meh' : 'Smile'
|
||||
!focused && (updateColor = theme.secondary)
|
||||
break
|
||||
return localActiveIndex !== null ? (
|
||||
<Image
|
||||
source={{ uri: localAccount?.avatarStatic }}
|
||||
style={{
|
||||
width: size + 2,
|
||||
height: size + 2,
|
||||
borderRadius: size,
|
||||
borderWidth: focused ? 2 : 0,
|
||||
borderColor: focused ? theme.secondary : color
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
name={focused ? 'Meh' : 'Smile'}
|
||||
size={size}
|
||||
color={!focused ? theme.secondary : color}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
name = 'AlertOctagon'
|
||||
break
|
||||
return <Icon name='AlertOctagon' size={size} color={color} />
|
||||
}
|
||||
return <Icon name={name} size={size} color={updateColor} />
|
||||
},
|
||||
...(Platform.OS === 'android' && {
|
||||
tabBarVisible:
|
||||
@ -208,7 +221,7 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
|
||||
getFocusedRouteNameFromRoute(route) !== 'Screen-Me-Switch'
|
||||
})
|
||||
}),
|
||||
[]
|
||||
[localActiveIndex, localAccount]
|
||||
)
|
||||
const tabNavigatorTabBarOptions = useMemo(
|
||||
() => ({
|
||||
|
@ -4,6 +4,7 @@ import { Blurhash } from 'gl-react-blurhash'
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
Image,
|
||||
ImageStyle,
|
||||
Pressable,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
@ -78,6 +79,7 @@ export interface Props {
|
||||
dimension?: { width: number; height: number }
|
||||
onPress?: () => void
|
||||
style?: StyleProp<ViewStyle>
|
||||
imageStyle?: StyleProp<ImageStyle>
|
||||
}
|
||||
|
||||
const GracefullyImage: React.FC<Props> = ({
|
||||
@ -87,7 +89,8 @@ const GracefullyImage: React.FC<Props> = ({
|
||||
blurhash,
|
||||
dimension,
|
||||
onPress,
|
||||
style
|
||||
style,
|
||||
imageStyle
|
||||
}) => {
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
@ -125,9 +128,16 @@ const GracefullyImage: React.FC<Props> = ({
|
||||
const children = useCallback(() => {
|
||||
if (imageVisible && !hidden) {
|
||||
if (cache) {
|
||||
return <ImageCache uri={imageVisible} style={styles.image} />
|
||||
return (
|
||||
<ImageCache uri={imageVisible} style={[styles.image, imageStyle]} />
|
||||
)
|
||||
} else {
|
||||
return <Image source={{ uri: imageVisible }} style={styles.image} />
|
||||
return (
|
||||
<Image
|
||||
source={{ uri: imageVisible }}
|
||||
style={[styles.image, imageStyle]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
} else if (blurhash) {
|
||||
return (
|
||||
@ -143,19 +153,17 @@ const GracefullyImage: React.FC<Props> = ({
|
||||
<Blurhash hash={blurhash} />
|
||||
</Surface>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<View
|
||||
style={[styles.image, { backgroundColor: theme.shimmerDefault }]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}, [hidden, mode, imageVisible])
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
children={children}
|
||||
style={[style, dimension && { ...dimension }]}
|
||||
style={[
|
||||
style,
|
||||
{ backgroundColor: theme.shimmerDefault },
|
||||
dimension && { ...dimension }
|
||||
]}
|
||||
{...(onPress
|
||||
? hidden
|
||||
? { disabled: true }
|
||||
|
@ -15,6 +15,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { Alert, Image, StyleSheet, Text, TextInput, View } from 'react-native'
|
||||
import { useQueryClient } from 'react-query'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import InstanceAuth from './Instance/Auth'
|
||||
import InstanceInfo from './Instance/Info'
|
||||
import { toast } from './toast'
|
||||
@ -209,47 +210,60 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
</Text>
|
||||
) : null}
|
||||
<View>
|
||||
<InstanceInfo
|
||||
visible={instanceQuery.data?.title !== undefined}
|
||||
header={t('server.information.name')}
|
||||
content={instanceQuery.data?.title || undefined}
|
||||
potentialWidth={10}
|
||||
/>
|
||||
<InstanceInfo
|
||||
visible={instanceQuery.data?.short_description !== undefined}
|
||||
header={t('server.information.description.heading')}
|
||||
content={instanceQuery.data?.short_description || undefined}
|
||||
potentialLines={5}
|
||||
/>
|
||||
<View style={styles.instanceStats}>
|
||||
<Placeholder
|
||||
{...(instanceQuery.isFetching && {
|
||||
Animation: props => (
|
||||
<Fade
|
||||
{...props}
|
||||
style={{ backgroundColor: theme.shimmerHighlight }}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
>
|
||||
<InstanceInfo
|
||||
style={styles.stat1}
|
||||
visible={instanceQuery.data?.stats?.user_count !== undefined}
|
||||
header={t('server.information.accounts')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.user_count?.toString() || undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
visible={instanceQuery.data?.title !== undefined}
|
||||
header={t('server.information.name')}
|
||||
content={instanceQuery.data?.title || undefined}
|
||||
potentialWidth={2}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={styles.stat2}
|
||||
visible={instanceQuery.data?.stats?.status_count !== undefined}
|
||||
header={t('server.information.statuses')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.status_count?.toString() || undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
visible={instanceQuery.data?.short_description !== undefined}
|
||||
header={t('server.information.description.heading')}
|
||||
content={instanceQuery.data?.short_description || undefined}
|
||||
potentialLines={5}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={styles.stat3}
|
||||
visible={instanceQuery.data?.stats?.domain_count !== undefined}
|
||||
header={t('server.information.domains')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.domain_count?.toString() || undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.instanceStats}>
|
||||
<InstanceInfo
|
||||
style={styles.stat1}
|
||||
visible={instanceQuery.data?.stats?.user_count !== undefined}
|
||||
header={t('server.information.accounts')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.user_count?.toString() || undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={styles.stat2}
|
||||
visible={instanceQuery.data?.stats?.status_count !== undefined}
|
||||
header={t('server.information.statuses')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.status_count?.toString() ||
|
||||
undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
<InstanceInfo
|
||||
style={styles.stat3}
|
||||
visible={instanceQuery.data?.stats?.domain_count !== undefined}
|
||||
header={t('server.information.domains')}
|
||||
content={
|
||||
instanceQuery.data?.stats?.domain_count?.toString() ||
|
||||
undefined
|
||||
}
|
||||
potentialWidth={4}
|
||||
/>
|
||||
</View>
|
||||
</Placeholder>
|
||||
{type === 'local' ? (
|
||||
<View style={styles.disclaimer}>
|
||||
<Icon
|
||||
@ -258,12 +272,12 @@ const ComponentInstance: React.FC<Props> = ({
|
||||
color={theme.secondary}
|
||||
style={styles.disclaimerIcon}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.disclaimerText, { color: theme.secondary }]}
|
||||
onPress={() => Linking.openURL('https://tooot.app/privacy')}
|
||||
>
|
||||
<Text style={[styles.disclaimerText, { color: theme.secondary }]}>
|
||||
{t('server.disclaimer')}
|
||||
<Text style={{ color: theme.blue }}>
|
||||
<Text
|
||||
style={{ color: theme.blue }}
|
||||
onPress={() => Linking.openURL('https://tooot.app/privacy')}
|
||||
>
|
||||
https://tooot.app/privacy
|
||||
</Text>
|
||||
</Text>
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { ParseHTML } from '@components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dimensions, StyleSheet, Text, View, ViewStyle } from 'react-native'
|
||||
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder'
|
||||
import { StyleSheet, Text, View, ViewStyle } from 'react-native'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
style?: ViewStyle
|
||||
@ -17,46 +16,36 @@ export interface Props {
|
||||
}
|
||||
|
||||
const InstanceInfo = React.memo(
|
||||
({
|
||||
style,
|
||||
visible,
|
||||
header,
|
||||
content,
|
||||
potentialWidth,
|
||||
potentialLines = 1
|
||||
}: Props) => {
|
||||
({ style, header, content, potentialWidth, potentialLines = 1 }: Props) => {
|
||||
const { t } = useTranslation('componentInstance')
|
||||
const { theme } = useTheme()
|
||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||
|
||||
return (
|
||||
<View style={[styles.base, style]}>
|
||||
<Text style={[styles.header, { color: theme.primary }]}>{header}</Text>
|
||||
<ShimmerPlaceholder
|
||||
visible={visible}
|
||||
stopAutoRun
|
||||
width={
|
||||
potentialWidth
|
||||
? potentialWidth * StyleConstants.Font.Size.M
|
||||
: Dimensions.get('screen').width -
|
||||
StyleConstants.Spacing.Global.PagePadding * 4
|
||||
}
|
||||
height={StyleConstants.Font.LineHeight.M * potentialLines}
|
||||
shimmerColors={[
|
||||
theme.shimmerDefault,
|
||||
theme.shimmerHighlight,
|
||||
theme.shimmerDefault
|
||||
]}
|
||||
>
|
||||
{content ? (
|
||||
<ParseHTML
|
||||
content={content}
|
||||
size={'M'}
|
||||
numberOfLines={5}
|
||||
expandHint={t('server.information.description.expandHint')}
|
||||
{content ? (
|
||||
<ParseHTML
|
||||
content={content}
|
||||
size={'M'}
|
||||
numberOfLines={5}
|
||||
expandHint={t('server.information.description.expandHint')}
|
||||
/>
|
||||
) : (
|
||||
Array.from(Array(potentialLines)).map((_, i) => (
|
||||
<PlaceholderLine
|
||||
key={i}
|
||||
width={
|
||||
potentialWidth
|
||||
? potentialWidth * StyleConstants.Font.Size.M
|
||||
: undefined
|
||||
}
|
||||
height={StyleConstants.Font.LineHeight.M}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
) : null}
|
||||
</ShimmerPlaceholder>
|
||||
))
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -27,15 +27,10 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
...StyleConstants.FontStyle[size],
|
||||
...(fontBold && { fontWeight: StyleConstants.Font.Weight.Bold })
|
||||
},
|
||||
imageContainer: {
|
||||
paddingVertical:
|
||||
(StyleConstants.Font.LineHeight[size] -
|
||||
StyleConstants.Font.Size[size]) /
|
||||
3
|
||||
},
|
||||
image: {
|
||||
width: StyleConstants.Font.Size[size],
|
||||
height: StyleConstants.Font.Size[size]
|
||||
height: StyleConstants.Font.Size[size],
|
||||
transform: [{ translateY: size === 'L' ? -3 : -1 }]
|
||||
}
|
||||
})
|
||||
}, [mode])
|
||||
@ -58,13 +53,11 @@ const ParseEmojis: React.FC<Props> = ({
|
||||
<Text key={i}>
|
||||
{/* When emoji starts a paragraph, lineHeight will break */}
|
||||
{i === 0 ? <Text> </Text> : null}
|
||||
<View style={styles.imageContainer}>
|
||||
<Image
|
||||
transitionDuration={0}
|
||||
uri={emojis[emojiIndex].url}
|
||||
style={[styles.image]}
|
||||
/>
|
||||
</View>
|
||||
<Image
|
||||
transitionDuration={0}
|
||||
uri={emojis[emojiIndex].url}
|
||||
style={[styles.image]}
|
||||
/>
|
||||
</Text>
|
||||
)
|
||||
} else {
|
||||
|
@ -104,7 +104,8 @@ const renderNode = ({
|
||||
key={index}
|
||||
style={{
|
||||
color: theme.blue,
|
||||
...StyleConstants.FontStyle[size]
|
||||
...StyleConstants.FontStyle[size],
|
||||
alignItems: 'center'
|
||||
}}
|
||||
onPress={async () =>
|
||||
!disableDetails && !shouldBeTag
|
||||
@ -114,14 +115,17 @@ const renderNode = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
{!shouldBeTag ? (
|
||||
<Icon
|
||||
color={theme.blue}
|
||||
name='ExternalLink'
|
||||
size={StyleConstants.Font.Size[size]}
|
||||
style={{
|
||||
transform: [{ translateY: size === 'L' ? -3 : -1 }]
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
{content || (showFullLink ? href : domain[1])}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
|
@ -125,7 +125,8 @@ const styles = StyleSheet.create({
|
||||
base: {
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
padding: StyleConstants.Spacing.Global.PagePadding
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingBottom: 0
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
|
@ -118,7 +118,7 @@ const TimelineDefault: React.FC<Props> = ({
|
||||
const styles = StyleSheet.create({
|
||||
statusView: {
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingBottom: StyleConstants.Spacing.S
|
||||
paddingBottom: 0
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
|
@ -123,7 +123,7 @@ const TimelineNotifications: React.FC<Props> = ({
|
||||
const styles = StyleSheet.create({
|
||||
notificationView: {
|
||||
padding: StyleConstants.Spacing.Global.PagePadding,
|
||||
paddingBottom: StyleConstants.Spacing.M
|
||||
paddingBottom: 0
|
||||
},
|
||||
header: {
|
||||
flex: 1,
|
||||
|
@ -110,7 +110,9 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'reblogged',
|
||||
currentValue: status.reblogged
|
||||
currentValue: status.reblogged,
|
||||
propertyCount: 'reblogs_count',
|
||||
countValue: status.reblogs_count
|
||||
}
|
||||
}),
|
||||
[status.reblogged]
|
||||
@ -124,7 +126,9 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'favourited',
|
||||
currentValue: status.favourited
|
||||
currentValue: status.favourited,
|
||||
propertyCount: 'favourites_count',
|
||||
countValue: status.favourites_count
|
||||
}
|
||||
}),
|
||||
[status.favourited]
|
||||
@ -138,7 +142,9 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
reblog,
|
||||
payload: {
|
||||
property: 'bookmarked',
|
||||
currentValue: status.bookmarked
|
||||
currentValue: status.bookmarked,
|
||||
propertyCount: undefined,
|
||||
countValue: undefined
|
||||
}
|
||||
}),
|
||||
[status.bookmarked]
|
||||
@ -156,7 +162,7 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.M,
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
@ -182,8 +188,8 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
{status.reblogs_count > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.M,
|
||||
color: iconColorAction(status.reblogged),
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
}}
|
||||
>
|
||||
@ -205,9 +211,10 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
{status.favourites_count > 0 && (
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.M,
|
||||
marginLeft: StyleConstants.Spacing.XS
|
||||
color: iconColorAction(status.favourited),
|
||||
fontSize: StyleConstants.Font.Size.M,
|
||||
marginLeft: StyleConstants.Spacing.XS,
|
||||
marginTop: 0
|
||||
}}
|
||||
>
|
||||
{status.favourites_count}
|
||||
@ -264,15 +271,14 @@ const TimelineActions: React.FC<Props> = ({ queryKey, status, reblog }) => {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
actions: {
|
||||
flexDirection: 'row',
|
||||
marginTop: StyleConstants.Spacing.S
|
||||
flexDirection: 'row'
|
||||
},
|
||||
action: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: StyleConstants.Spacing.S
|
||||
minHeight: StyleConstants.Font.Size.L + StyleConstants.Spacing.S * 4
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -54,9 +54,11 @@ const TimelineHeaderNotification: React.FC<Props> = ({
|
||||
/>
|
||||
<View style={styles.meta}>
|
||||
<HeaderSharedCreated created_at={notification.created_at} />
|
||||
<HeaderSharedVisibility
|
||||
visibility={notification.status?.visibility}
|
||||
/>
|
||||
{notification.status?.visibility ? (
|
||||
<HeaderSharedVisibility
|
||||
visibility={notification.status.visibility}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||
<HeaderSharedApplication
|
||||
application={notification.status?.application}
|
||||
|
@ -5,20 +5,34 @@ import React from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
visibility?: Mastodon.Status['visibility']
|
||||
visibility: Mastodon.Status['visibility']
|
||||
}
|
||||
|
||||
const HeaderSharedVisibility: React.FC<Props> = ({ visibility }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return visibility && visibility === 'private' ? (
|
||||
<Icon
|
||||
name='Lock'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
) : null
|
||||
switch (visibility) {
|
||||
case 'private':
|
||||
return (
|
||||
<Icon
|
||||
name='Lock'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
)
|
||||
case 'direct':
|
||||
return (
|
||||
<Icon
|
||||
name='Mail'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.visibility}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -1,5 +1,6 @@
|
||||
export default {
|
||||
content: {
|
||||
moved: 'User moved',
|
||||
created_at: 'Registered: {{date}}',
|
||||
summary: {
|
||||
statuses_count: '{{count}} toots',
|
||||
|
@ -1,5 +1,6 @@
|
||||
export default {
|
||||
content: {
|
||||
moved: '账户已迁移',
|
||||
created_at: '注册时间:{{date}}',
|
||||
summary: {
|
||||
statuses_count: '{{count}} 条嘟文',
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Button from '@components/Button'
|
||||
import ComponentInstance from '@components/Instance'
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { useAccountCheckQuery } from '@utils/queryHooks/accountCheck'
|
||||
import {
|
||||
getLocalActiveIndex,
|
||||
getLocalInstances,
|
||||
@ -38,19 +37,15 @@ const AccountButton: React.FC<Props> = ({
|
||||
const queryClient = useQueryClient()
|
||||
const navigation = useNavigation()
|
||||
const dispatch = useDispatch()
|
||||
const { isLoading, data } = useAccountCheckQuery({
|
||||
id: instance.account.id,
|
||||
index,
|
||||
options: { retry: false }
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
type='text'
|
||||
disabled={disabled}
|
||||
loading={isLoading}
|
||||
style={styles.button}
|
||||
content={`@${data?.acct || '...'}@${instance.uri}${disabled ? ' ✓' : ''}`}
|
||||
content={`@${instance.account.acct}@${instance.uri}${
|
||||
disabled ? ' ✓' : ''
|
||||
}`}
|
||||
onPress={() => {
|
||||
dispatch(localUpdateActiveIndex(index))
|
||||
queryClient.clear()
|
||||
|
@ -25,8 +25,6 @@ import accountInitialState from './Account/utils/initialState'
|
||||
import accountReducer from './Account/utils/reducer'
|
||||
import { SharedAccountProp } from './sharedScreens'
|
||||
|
||||
// Moved account example: https://m.cmx.im/web/accounts/27812
|
||||
|
||||
const ScreenSharedAccount: React.FC<SharedAccountProp> = ({
|
||||
route: {
|
||||
params: { account }
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React, { createRef, useEffect } from 'react'
|
||||
import { Animated, StyleSheet, View } from 'react-native'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import React, { useCallback } from 'react'
|
||||
import { StyleSheet, View } from 'react-native'
|
||||
import { Placeholder, Fade } from 'rn-placeholder'
|
||||
import AccountInformationAccount from './Information/Account'
|
||||
import AccountInformationActions from './Information/Actions'
|
||||
import AccountInformationAvatar from './Information/Avatar'
|
||||
@ -20,65 +22,50 @@ const AccountInformation: React.FC<Props> = ({
|
||||
account,
|
||||
ownAccount = false
|
||||
}) => {
|
||||
const shimmerNameRef = createRef<any>()
|
||||
const shimmerAccountRef = createRef<any>()
|
||||
const shimmerCreatedRef = createRef<any>()
|
||||
const shimmerStatsRef = createRef<any>()
|
||||
useEffect(() => {
|
||||
const informationAnimated = Animated.stagger(400, [
|
||||
Animated.parallel([
|
||||
shimmerNameRef.current?.getAnimated(),
|
||||
shimmerAccountRef.current?.getAnimated(),
|
||||
shimmerCreatedRef.current?.getAnimated(),
|
||||
shimmerStatsRef.current?.ref1.getAnimated(),
|
||||
shimmerStatsRef.current?.ref2.getAnimated(),
|
||||
shimmerStatsRef.current?.ref3.getAnimated()
|
||||
])
|
||||
])
|
||||
Animated.loop(informationAnimated).start()
|
||||
}, [])
|
||||
const { mode, theme } = useTheme()
|
||||
|
||||
const animation = useCallback(
|
||||
props => (
|
||||
<Fade {...props} style={{ backgroundColor: theme.shimmerHighlight }} />
|
||||
),
|
||||
[mode]
|
||||
)
|
||||
|
||||
return (
|
||||
<View style={styles.base}>
|
||||
{/* <Text>Moved or not: {account.moved}</Text> */}
|
||||
<View style={styles.avatarAndActions}>
|
||||
<AccountInformationAvatar account={account} />
|
||||
<View style={styles.actions}>
|
||||
{ownAccount ? (
|
||||
<AccountInformationSwitch />
|
||||
) : (
|
||||
<AccountInformationActions account={account} />
|
||||
)}
|
||||
<Placeholder Animation={animation}>
|
||||
<View style={styles.avatarAndActions}>
|
||||
<AccountInformationAvatar account={account} />
|
||||
<View style={styles.actions}>
|
||||
{ownAccount ? (
|
||||
<AccountInformationSwitch />
|
||||
) : (
|
||||
<AccountInformationActions account={account} />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<AccountInformationName ref={shimmerNameRef} account={account} />
|
||||
<AccountInformationName account={account} />
|
||||
|
||||
<AccountInformationAccount
|
||||
ref={shimmerAccountRef}
|
||||
account={account}
|
||||
ownAccount={ownAccount}
|
||||
/>
|
||||
<AccountInformationAccount account={account} ownAccount={ownAccount} />
|
||||
|
||||
{!ownAccount ? (
|
||||
<>
|
||||
{account?.fields && account.fields.length > 0 ? (
|
||||
<AccountInformationFields account={account} />
|
||||
) : null}
|
||||
{account?.note &&
|
||||
account.note.length > 0 &&
|
||||
account.note !== '<p></p>' ? (
|
||||
// Empty notes might generate empty p tag
|
||||
<AccountInformationNotes account={account} />
|
||||
) : null}
|
||||
<AccountInformationCreated
|
||||
ref={shimmerCreatedRef}
|
||||
account={account}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{!ownAccount ? (
|
||||
<>
|
||||
{account?.fields && account.fields.length > 0 ? (
|
||||
<AccountInformationFields account={account} />
|
||||
) : null}
|
||||
{account?.note &&
|
||||
account.note.length > 0 &&
|
||||
account.note !== '<p></p>' ? (
|
||||
// Empty notes might generate empty p tag
|
||||
<AccountInformationNotes account={account} />
|
||||
) : null}
|
||||
<AccountInformationCreated account={account} />
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<AccountInformationStats ref={shimmerStatsRef} account={account} />
|
||||
<AccountInformationStats account={account} ownAccount={ownAccount} />
|
||||
</Placeholder>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
@ -1,79 +1,109 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { getLocalUri } from '@utils/slices/instancesSlice'
|
||||
import { getLocalAccount, getLocalUri } from '@utils/slices/instancesSlice'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { forwardRef } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import ShimmerPlaceholder, {
|
||||
createShimmerPlaceholder
|
||||
} from 'react-native-shimmer-placeholder'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
ownAccount?: boolean
|
||||
}
|
||||
|
||||
const AccountInformationAccount = forwardRef<ShimmerPlaceholder, Props>(
|
||||
({ account, ownAccount }, ref) => {
|
||||
const { theme } = useTheme()
|
||||
const localUri = useSelector(getLocalUri)
|
||||
const AccountInformationAccount: React.FC<Props> = ({
|
||||
account,
|
||||
ownAccount
|
||||
}) => {
|
||||
const { theme } = useTheme()
|
||||
const localAccount = useSelector(getLocalAccount)
|
||||
const localUri = useSelector(getLocalUri)
|
||||
|
||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||
const movedStyle = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
base: {
|
||||
textDecorationLine: account?.moved ? 'line-through' : undefined
|
||||
}
|
||||
}),
|
||||
[account?.moved]
|
||||
)
|
||||
const movedContent = useMemo(() => {
|
||||
if (account?.moved) {
|
||||
return (
|
||||
<Text
|
||||
style={[
|
||||
styles.moved,
|
||||
{ color: theme.secondary, ...StyleConstants.FontStyle.M }
|
||||
]}
|
||||
selectable
|
||||
>
|
||||
@{account.moved.acct}
|
||||
</Text>
|
||||
)
|
||||
}
|
||||
}, [account?.moved])
|
||||
|
||||
if (account || (ownAccount && localAccount !== undefined)) {
|
||||
return (
|
||||
<ShimmerPlaceholder
|
||||
ref={ref}
|
||||
visible={account?.acct !== undefined}
|
||||
width={StyleConstants.Font.Size.M * 8}
|
||||
height={StyleConstants.Font.LineHeight.M}
|
||||
style={{ marginBottom: StyleConstants.Spacing.L }}
|
||||
shimmerColors={[
|
||||
theme.shimmerDefault,
|
||||
theme.shimmerHighlight,
|
||||
theme.shimmerDefault
|
||||
]}
|
||||
<View
|
||||
style={[styles.base, { flexDirection: 'row', alignItems: 'center' }]}
|
||||
>
|
||||
<View style={styles.account}>
|
||||
<Text
|
||||
style={{
|
||||
<Text
|
||||
style={[
|
||||
movedStyle.base,
|
||||
{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.M
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
@{account?.acct}
|
||||
{ownAccount ? `@${localUri}` : null}
|
||||
</Text>
|
||||
{account?.locked ? (
|
||||
<Icon
|
||||
name='Lock'
|
||||
style={styles.type}
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
/>
|
||||
) : null}
|
||||
{account?.bot ? (
|
||||
<Icon
|
||||
name='HardDrive'
|
||||
style={styles.type}
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</ShimmerPlaceholder>
|
||||
}
|
||||
]}
|
||||
selectable
|
||||
>
|
||||
@{ownAccount ? localAccount?.acct : account?.acct}
|
||||
{ownAccount ? `@${localUri}` : null}
|
||||
</Text>
|
||||
{movedContent}
|
||||
{account?.locked ? (
|
||||
<Icon
|
||||
name='Lock'
|
||||
style={styles.type}
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
/>
|
||||
) : null}
|
||||
{account?.bot ? (
|
||||
<Icon
|
||||
name='HardDrive'
|
||||
style={styles.type}
|
||||
color={theme.secondary}
|
||||
size={StyleConstants.Font.Size.M}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.M * 2}
|
||||
height={StyleConstants.Font.LineHeight.M}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={styles.base}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
account: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
base: {
|
||||
borderRadius: 0,
|
||||
marginBottom: StyleConstants.Spacing.L
|
||||
},
|
||||
type: { marginLeft: StyleConstants.Spacing.S }
|
||||
type: { marginLeft: StyleConstants.Spacing.S },
|
||||
moved: {
|
||||
marginLeft: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default React.memo(
|
||||
|
@ -4,12 +4,29 @@ import { useNavigation } from '@react-navigation/native'
|
||||
import { useRelationshipQuery } from '@utils/queryHooks/relationship'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
}
|
||||
|
||||
const GoToMoved = ({ account }: { account: Mastodon.Account }) => {
|
||||
const { t } = useTranslation('sharedAccount')
|
||||
const navigation = useNavigation()
|
||||
const query = useRelationshipQuery({ id: account.id })
|
||||
|
||||
return query.data && !query.data.blocked_by ? (
|
||||
<Button
|
||||
type='text'
|
||||
content={t('content.moved')}
|
||||
onPress={() =>
|
||||
navigation.push('Screen-Shared-Account', { account: account.moved })
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
|
||||
const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
||||
const navigation = useNavigation()
|
||||
const query = useRelationshipQuery({ id: account.id })
|
||||
@ -32,10 +49,14 @@ const Conversation = ({ account }: { account: Mastodon.Account }) => {
|
||||
|
||||
const AccountInformationActions: React.FC<Props> = ({ account }) => {
|
||||
return account && account.id ? (
|
||||
<>
|
||||
<Conversation account={account} />
|
||||
<RelationshipOutgoing id={account.id} />
|
||||
</>
|
||||
account.moved ? (
|
||||
<GoToMoved account={account} />
|
||||
) : (
|
||||
<>
|
||||
<Conversation account={account} />
|
||||
<RelationshipOutgoing id={account.id} />
|
||||
</>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
||||
|
@ -1,27 +1,29 @@
|
||||
import GracefullyImage from '@components/GracefullyImage'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import React from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
}
|
||||
|
||||
const AccountInformationAvatar = React.memo(
|
||||
({ account }: Props) => {
|
||||
return (
|
||||
<GracefullyImage
|
||||
style={styles.base}
|
||||
uri={{ original: account?.avatar }}
|
||||
dimension={{
|
||||
width: StyleConstants.Avatar.L,
|
||||
height: StyleConstants.Avatar.L
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
(_, next) => next.account === undefined
|
||||
)
|
||||
const AccountInformationAvatar: React.FC<Props> = ({ account }) => {
|
||||
const dimension = useMemo(
|
||||
() => ({
|
||||
width: StyleConstants.Avatar.L,
|
||||
height: StyleConstants.Avatar.L
|
||||
}),
|
||||
[]
|
||||
)
|
||||
|
||||
return (
|
||||
<GracefullyImage
|
||||
style={styles.base}
|
||||
uri={{ original: account?.avatar }}
|
||||
dimension={dimension}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: { borderRadius: 8, overflow: 'hidden' }
|
||||
|
@ -1,76 +1,74 @@
|
||||
import Icon from '@components/Icon'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { forwardRef } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import ShimmerPlaceholder, {
|
||||
createShimmerPlaceholder
|
||||
} from 'react-native-shimmer-placeholder'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
}
|
||||
|
||||
const AccountInformationCreated = forwardRef<ShimmerPlaceholder, Props>(
|
||||
({ account }, ref) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('sharedAccount')
|
||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||
const AccountInformationCreated: React.FC<Props> = ({ account }) => {
|
||||
const { i18n } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('sharedAccount')
|
||||
|
||||
if (account) {
|
||||
return (
|
||||
<ShimmerPlaceholder
|
||||
ref={ref}
|
||||
visible={account?.created_at !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 8}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
style={{ marginBottom: StyleConstants.Spacing.M }}
|
||||
shimmerColors={[
|
||||
theme.shimmerDefault,
|
||||
theme.shimmerHighlight,
|
||||
theme.shimmerDefault
|
||||
]}
|
||||
<View
|
||||
style={[styles.base, { flexDirection: 'row', alignItems: 'center' }]}
|
||||
>
|
||||
<View style={styles.created}>
|
||||
<Icon
|
||||
name='Calendar'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.S
|
||||
}}
|
||||
>
|
||||
{t('content.created_at', {
|
||||
date: new Date(account?.created_at || '').toLocaleDateString(
|
||||
i18n.language,
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}
|
||||
)
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
</ShimmerPlaceholder>
|
||||
<Icon
|
||||
name='Calendar'
|
||||
size={StyleConstants.Font.Size.S}
|
||||
color={theme.secondary}
|
||||
style={styles.icon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.secondary,
|
||||
...StyleConstants.FontStyle.S
|
||||
}}
|
||||
>
|
||||
{t('content.created_at', {
|
||||
date: new Date(account?.created_at || '').toLocaleDateString(
|
||||
i18n.language,
|
||||
{
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
}
|
||||
)
|
||||
})}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.S * 3}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={styles.base}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
created: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
base: {
|
||||
borderRadius: 0,
|
||||
marginBottom: StyleConstants.Spacing.M
|
||||
},
|
||||
icon: {
|
||||
marginRight: StyleConstants.Spacing.XS
|
||||
}
|
||||
})
|
||||
|
||||
export default AccountInformationCreated
|
||||
export default React.memo(
|
||||
AccountInformationCreated,
|
||||
(_, next) => next.account === undefined
|
||||
)
|
||||
|
@ -1,52 +1,80 @@
|
||||
import { ParseEmojis } from '@components/Parse'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { useTheme } from '@utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { forwardRef } from 'react'
|
||||
import { StyleSheet } from 'react-native'
|
||||
import ShimmerPlaceholder, {
|
||||
createShimmerPlaceholder
|
||||
} from 'react-native-shimmer-placeholder'
|
||||
import React, { useMemo } from 'react'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
}
|
||||
|
||||
const AccountInformationName = forwardRef<ShimmerPlaceholder, Props>(
|
||||
({ account }, ref) => {
|
||||
const { theme } = useTheme()
|
||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||
const AccountInformationName: React.FC<Props> = ({ account }) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<ShimmerPlaceholder
|
||||
ref={ref}
|
||||
visible={
|
||||
account?.display_name !== undefined || account?.username !== undefined
|
||||
const movedStyle = useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
base: {
|
||||
textDecorationLine: account?.moved ? 'line-through' : undefined
|
||||
}
|
||||
width={StyleConstants.Font.Size.L * 8}
|
||||
height={StyleConstants.Font.LineHeight.L}
|
||||
style={styles.name}
|
||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
||||
>
|
||||
{account ? (
|
||||
}),
|
||||
[account?.moved]
|
||||
)
|
||||
const movedContent = useMemo(() => {
|
||||
if (account?.moved) {
|
||||
return (
|
||||
<View style={styles.moved}>
|
||||
<ParseEmojis
|
||||
content={account.moved.display_name || account.moved.username}
|
||||
emojis={account.moved.emojis}
|
||||
size='L'
|
||||
fontBold
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}, [account?.moved])
|
||||
|
||||
if (account) {
|
||||
return (
|
||||
<View style={[styles.base, { flexDirection: 'row' }]}>
|
||||
<Text style={movedStyle.base}>
|
||||
<ParseEmojis
|
||||
content={account.display_name || account.username}
|
||||
emojis={account.emojis}
|
||||
size='L'
|
||||
fontBold
|
||||
/>
|
||||
) : null}
|
||||
</ShimmerPlaceholder>
|
||||
</Text>
|
||||
{movedContent}
|
||||
</View>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.L * 2}
|
||||
height={StyleConstants.Font.LineHeight.L}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={styles.base}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
name: {
|
||||
flexDirection: 'row',
|
||||
base: {
|
||||
borderRadius: 0,
|
||||
marginTop: StyleConstants.Spacing.M,
|
||||
marginBottom: StyleConstants.Spacing.XS
|
||||
},
|
||||
moved: {
|
||||
marginLeft: StyleConstants.Spacing.S
|
||||
}
|
||||
})
|
||||
|
||||
export default AccountInformationName
|
||||
export default React.memo(
|
||||
AccountInformationName,
|
||||
(_, next) => next.account === undefined
|
||||
)
|
||||
|
@ -1,106 +1,93 @@
|
||||
import { useNavigation } from '@react-navigation/native'
|
||||
import { StyleConstants } from '@root/utils/styles/constants'
|
||||
import { useTheme } from '@root/utils/styles/ThemeManager'
|
||||
import { LinearGradient } from 'expo-linear-gradient'
|
||||
import React, { createRef, forwardRef, useImperativeHandle } from 'react'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { StyleSheet, Text, View } from 'react-native'
|
||||
import ShimmerPlaceholder, {
|
||||
createShimmerPlaceholder
|
||||
} from 'react-native-shimmer-placeholder'
|
||||
import { PlaceholderLine } from 'rn-placeholder'
|
||||
|
||||
export interface Props {
|
||||
account: Mastodon.Account | undefined
|
||||
ownAccount?: boolean
|
||||
}
|
||||
|
||||
const AccountInformationStats = forwardRef<any, Props>(({ account }, ref) => {
|
||||
const AccountInformationStats: React.FC<Props> = ({ account, ownAccount }) => {
|
||||
const navigation = useNavigation()
|
||||
const { theme } = useTheme()
|
||||
const { t } = useTranslation('sharedAccount')
|
||||
const ShimmerPlaceholder = createShimmerPlaceholder(LinearGradient)
|
||||
|
||||
const ref1 = createRef<ShimmerPlaceholder>()
|
||||
const ref2 = createRef<ShimmerPlaceholder>()
|
||||
const ref3 = createRef<ShimmerPlaceholder>()
|
||||
useImperativeHandle(ref, () => ({
|
||||
get ref1 () {
|
||||
return ref1.current
|
||||
},
|
||||
get ref2 () {
|
||||
return ref2.current
|
||||
},
|
||||
get ref3 () {
|
||||
return ref3.current
|
||||
}
|
||||
}))
|
||||
|
||||
return (
|
||||
<View style={styles.stats}>
|
||||
<ShimmerPlaceholder
|
||||
ref={ref1}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
||||
>
|
||||
<Text style={[styles.stat, { color: theme.primary }]}>
|
||||
{t('content.summary.statuses_count', {
|
||||
<View style={[styles.stats, { flexDirection: 'row' }]}>
|
||||
{account ? (
|
||||
<Text
|
||||
style={[styles.stat, { color: theme.primary }]}
|
||||
children={t('content.summary.statuses_count', {
|
||||
count: account?.statuses_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
<ShimmerPlaceholder
|
||||
ref={ref2}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
||||
>
|
||||
onPress={() =>
|
||||
ownAccount && navigation.push('Screen-Shared-Account', { account })
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.S * 1.25}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
)}
|
||||
{account ? (
|
||||
<Text
|
||||
style={[styles.stat, { color: theme.primary, textAlign: 'right' }]}
|
||||
children={t('content.summary.following_count', {
|
||||
count: account?.following_count || 0
|
||||
})}
|
||||
onPress={() =>
|
||||
account &&
|
||||
navigation.push('Screen-Shared-Relationships', {
|
||||
account,
|
||||
initialType: 'following'
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('content.summary.following_count', {
|
||||
count: account?.following_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
<ShimmerPlaceholder
|
||||
ref={ref3}
|
||||
visible={account !== undefined}
|
||||
width={StyleConstants.Font.Size.S * 5}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
shimmerColors={[theme.shimmerDefault, theme.shimmerHighlight, theme.shimmerDefault]}
|
||||
>
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.S * 1.25}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
)}
|
||||
{account ? (
|
||||
<Text
|
||||
style={[styles.stat, { color: theme.primary, textAlign: 'center' }]}
|
||||
children={t('content.summary.followers_count', {
|
||||
count: account?.followers_count || 0
|
||||
})}
|
||||
onPress={() =>
|
||||
account &&
|
||||
navigation.push('Screen-Shared-Relationships', {
|
||||
account,
|
||||
initialType: 'followers'
|
||||
})
|
||||
}
|
||||
>
|
||||
{t('content.summary.followers_count', {
|
||||
count: account?.followers_count || 0
|
||||
})}
|
||||
</Text>
|
||||
</ShimmerPlaceholder>
|
||||
/>
|
||||
) : (
|
||||
<PlaceholderLine
|
||||
width={StyleConstants.Font.Size.S * 1.25}
|
||||
height={StyleConstants.Font.LineHeight.S}
|
||||
color={theme.shimmerDefault}
|
||||
noMargin
|
||||
style={{ borderRadius: 0 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
stats: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between'
|
||||
},
|
||||
stat: {
|
||||
|
@ -2,49 +2,14 @@ import { HeaderLeft, HeaderRight } from '@components/Header'
|
||||
import { StyleConstants } from '@utils/styles/constants'
|
||||
import { findIndex } from 'lodash'
|
||||
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 { IImageInfo } from 'react-native-image-zoom-viewer/built/image-viewer.type'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
|
||||
import { SharedImagesViewerProp } from './sharedScreens'
|
||||
|
||||
const Stack = createNativeStackNavigator()
|
||||
|
||||
const TheImage = ({
|
||||
style,
|
||||
source,
|
||||
imageUrls
|
||||
}: {
|
||||
style: any
|
||||
source: { uri: string }
|
||||
imageUrls: (IImageInfo & {
|
||||
preview_url: Mastodon.AttachmentImage['preview_url']
|
||||
remote_url: Mastodon.AttachmentImage['remote_url']
|
||||
imageIndex: number
|
||||
})[]
|
||||
}) => {
|
||||
const [imageVisible, setImageVisible] = useState(false)
|
||||
Image.getSize(source.uri, () => setImageVisible(true))
|
||||
return (
|
||||
<Image
|
||||
style={style}
|
||||
source={{
|
||||
uri: imageVisible
|
||||
? source.uri
|
||||
: imageUrls[findIndex(imageUrls, ['url', source.uri])].preview_url
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
|
||||
route: {
|
||||
params: { imageUrls, imageIndex }
|
||||
@ -64,15 +29,17 @@ const ScreenSharedImagesViewer: React.FC<SharedImagesViewerProp> = ({
|
||||
index={initialIndex}
|
||||
imageUrls={imageUrls}
|
||||
pageAnimateTime={250}
|
||||
enableSwipeDown={true}
|
||||
useNativeDriver={true}
|
||||
enableSwipeDown
|
||||
useNativeDriver
|
||||
swipeDownThreshold={100}
|
||||
renderIndicator={() => <></>}
|
||||
saveToLocalByLongPress={false}
|
||||
onSwipeDown={() => navigation.goBack()}
|
||||
style={{ flex: 1, marginBottom: 44 + safeAreaInsets.bottom }}
|
||||
onChange={index => index !== undefined && setCurrentIndex(index)}
|
||||
renderImage={props => <TheImage {...props} imageUrls={imageUrls} />}
|
||||
renderImage={prop => {
|
||||
return <Image {...prop} resizeMode={'contain'} />
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
|
@ -1,7 +1,10 @@
|
||||
import client from '@api/client'
|
||||
import NetInfo from '@react-native-community/netinfo'
|
||||
import { store } from '@root/store'
|
||||
import { localRemoveInstance } from '@utils/slices/instancesSlice'
|
||||
import {
|
||||
localRemoveInstance,
|
||||
localUpdateAccount
|
||||
} from '@utils/slices/instancesSlice'
|
||||
import log from './log'
|
||||
|
||||
const netInfo = async (): Promise<{
|
||||
@ -31,6 +34,12 @@ const netInfo = async (): Promise<{
|
||||
store.dispatch(localRemoveInstance(activeIndex))
|
||||
return Promise.resolve({ connected: true, corruputed: '' })
|
||||
} else {
|
||||
store.dispatch(
|
||||
localUpdateAccount({
|
||||
acct: res.acct,
|
||||
avatarStatic: res.avatar_static
|
||||
})
|
||||
)
|
||||
return Promise.resolve({ connected: true })
|
||||
}
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import log from './log'
|
||||
|
||||
const sentry = () => {
|
||||
log('log', 'Sentry', 'initializing')
|
||||
return Sentry.init({
|
||||
Sentry.init({
|
||||
dsn: Constants.manifest.extra.sentryDSN,
|
||||
enableInExpoDevelopment: false,
|
||||
debug: __DEV__
|
||||
|
@ -242,8 +242,16 @@ export type MutationVarsTimelineUpdateStatusProperty = {
|
||||
reblog?: boolean
|
||||
payload:
|
||||
| {
|
||||
property: 'bookmarked' | 'favourited' | 'muted' | 'pinned' | 'reblogged'
|
||||
property: 'bookmarked' | 'muted' | 'pinned'
|
||||
currentValue: boolean
|
||||
propertyCount: undefined
|
||||
countValue: undefined
|
||||
}
|
||||
| {
|
||||
property: 'favourited' | 'reblogged'
|
||||
currentValue: boolean
|
||||
propertyCount: 'favourites_count' | 'reblogs_count'
|
||||
countValue: number
|
||||
}
|
||||
| {
|
||||
property: 'poll'
|
||||
|
@ -19,6 +19,13 @@ const updateConversation = ({
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
if (payload.propertyCount) {
|
||||
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||
item.last_status[payload.propertyCount] = payload.countValue - 1
|
||||
} else {
|
||||
item.last_status[payload.propertyCount] = payload.countValue + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
@ -16,6 +16,13 @@ const updateNotification = ({
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
if (payload.propertyCount) {
|
||||
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||
item.status[payload.propertyCount] = payload.countValue - 1
|
||||
} else {
|
||||
item.status[payload.propertyCount] = payload.countValue + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
@ -23,11 +23,25 @@ const updateStatus = ({
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
if (payload.propertyCount) {
|
||||
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||
item.reblog![payload.propertyCount] = payload.countValue - 1
|
||||
} else {
|
||||
item.reblog![payload.propertyCount] = payload.countValue + 1
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item[payload.property] =
|
||||
typeof payload.currentValue === 'boolean'
|
||||
? !payload.currentValue
|
||||
: true
|
||||
if (payload.propertyCount) {
|
||||
if (typeof payload.currentValue === 'boolean' && payload.currentValue) {
|
||||
item[payload.propertyCount] = payload.countValue - 1
|
||||
} else {
|
||||
item[payload.propertyCount] = payload.countValue + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ export type InstanceLocal = {
|
||||
uri: Mastodon.Instance['uri']
|
||||
account: {
|
||||
id: Mastodon.Account['id']
|
||||
acct: Mastodon.Account['acct']
|
||||
avatarStatic: Mastodon.Account['avatar_static']
|
||||
preferences: Mastodon.Preferences
|
||||
}
|
||||
notification: {
|
||||
@ -64,7 +66,7 @@ export const localAddInstance = createAsyncThunk(
|
||||
const instanceLocal: InstancesState['local'] = store.getState().instances
|
||||
.local
|
||||
|
||||
const { id } = await client<Mastodon.Account>({
|
||||
const { id, acct, avatar_static } = await client<Mastodon.Account>({
|
||||
method: 'get',
|
||||
instance: 'remote',
|
||||
instanceDomain: url,
|
||||
@ -108,6 +110,8 @@ export const localAddInstance = createAsyncThunk(
|
||||
uri,
|
||||
account: {
|
||||
id,
|
||||
acct,
|
||||
avatarStatic: avatar_static,
|
||||
preferences
|
||||
},
|
||||
notification: {
|
||||
@ -182,6 +186,19 @@ const instancesSlice = createSlice({
|
||||
throw new Error('Set index cannot be found')
|
||||
}
|
||||
},
|
||||
localUpdateAccount: (
|
||||
state,
|
||||
action: PayloadAction<
|
||||
Pick<InstanceLocal['account'], 'acct' & 'avatarStatic'>
|
||||
>
|
||||
) => {
|
||||
if (state.local.activeIndex !== null) {
|
||||
state.local.instances[state.local.activeIndex].account = {
|
||||
...state.local.instances[state.local.activeIndex].account,
|
||||
...action.payload
|
||||
}
|
||||
}
|
||||
},
|
||||
localUpdateNotification: (
|
||||
state,
|
||||
action: PayloadAction<Partial<InstanceLocal['notification']>>
|
||||
@ -273,6 +290,7 @@ export const getRemoteUrl = ({ instances: { remote } }: RootState) => remote.url
|
||||
|
||||
export const {
|
||||
localUpdateActiveIndex,
|
||||
localUpdateAccount,
|
||||
localUpdateNotification,
|
||||
remoteUpdate
|
||||
} = instancesSlice.actions
|
||||
|
@ -9212,6 +9212,11 @@ rimraf@~2.2.6:
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582"
|
||||
integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=
|
||||
|
||||
rn-placeholder@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/rn-placeholder/-/rn-placeholder-3.0.3.tgz#98f635b263ee003af2a984eed32d86ade308df35"
|
||||
integrity sha512-EmVeLT8zDcTPilQZ2OHO/IiYUy2gApKGgbshDZBX0C4qxsn0cFATwgwOwyz8O7Vwg1Hul97Ci95hu7d6Js6XMQ==
|
||||
|
||||
rsvp@^4.8.4:
|
||||
version "4.8.5"
|
||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||
|
Loading…
x
Reference in New Issue
Block a user