mirror of
https://github.com/tooot-app/app
synced 2025-06-05 22:19:13 +02:00
Updates
This commit is contained in:
@ -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({
|
||||
|
Reference in New Issue
Block a user