1
0
mirror of https://github.com/tooot-app/app synced 2025-06-05 22:19:13 +02:00
This commit is contained in:
Zhiyuan Zheng
2021-01-23 02:41:50 +01:00
parent aa467f6911
commit 86231fb7b7
36 changed files with 614 additions and 481 deletions

View File

@ -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 }

View File

@ -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>

View File

@ -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>
)
}

View File

@ -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 {

View File

@ -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>
)
}

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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
}
})

View File

@ -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}

View File

@ -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({