This commit is contained in:
Zhiyuan Zheng 2021-01-04 14:55:34 +01:00
parent e9ea0ed71e
commit 811964e10f
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
15 changed files with 136 additions and 98 deletions

View File

@ -102,7 +102,11 @@ const Index: React.FC<Props> = ({ localCorrupt }) => {
const queryNotification = useInfiniteQuery(
['Notifications', {}],
timelineFetch,
{ enabled: localInstance ? true : false }
{
enabled: localInstance ? true : false,
refetchInterval: 1000 * 60,
refetchIntervalInBackground: true
}
)
const prevNotification = useSelector(getLocalNotification)
useEffect(() => {

View File

@ -41,6 +41,7 @@ const client = async ({
instance === 'remote' ? instanceDomain || state.remote.url : state.local.url
return axios({
timeout: method === 'post' ? 1000 * 60 : 1000 * 15,
method,
baseURL: `https://${domain}/api/${version}/`,
url,

View File

@ -1,3 +1,4 @@
import { StyleConstants } from '@root/utils/styles/constants'
import React, { createElement } from 'react'
import { StyleProp, View, ViewStyle } from 'react-native'
import * as FeatherIcon from 'react-native-feather'
@ -8,7 +9,6 @@ export interface Props {
color: string
fill?: string
strokeWidth?: number
inline?: boolean // When used in line of text, need to drag it down
style?: StyleProp<ViewStyle>
}
@ -18,7 +18,6 @@ const Icon: React.FC<Props> = ({
color,
fill,
strokeWidth = 2,
inline = false,
style
}) => {
return (
@ -29,8 +28,7 @@ const Icon: React.FC<Props> = ({
width: size,
height: size,
justifyContent: 'center',
alignItems: 'center',
marginBottom: inline ? -size * 0.125 : undefined
alignItems: 'center'
}
]}
>

View File

@ -27,8 +27,7 @@ const ParseEmojis: React.FC<Props> = ({
},
image: {
width: StyleConstants.Font.Size[size],
height: StyleConstants.Font.Size[size],
marginBottom: -StyleConstants.Font.Size[size] * 0.125
height: StyleConstants.Font.Size[size]
}
})
@ -51,7 +50,6 @@ const ParseEmojis: React.FC<Props> = ({
{/* When emoji starts a paragraph, lineHeight will break */}
{i === 0 ? <Text> </Text> : null}
<Image
// resizeMode='contain'
source={{ uri: emojis[emojiIndex].url }}
style={[styles.image]}
/>

View File

@ -9,6 +9,8 @@ import React, { useCallback, useMemo, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import HTMLView from 'react-native-htmlview'
import Animated, {
measure,
useAnimatedRef,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
@ -103,7 +105,6 @@ const renderNode = ({
>
{!shouldBeTag ? (
<Icon
inline
color={theme.blue}
name='ExternalLink'
size={StyleConstants.Font.Size[size]}
@ -162,7 +163,13 @@ const ParseHTML: React.FC<Props> = ({
)
const textComponent = useCallback(({ children }) => {
if (children) {
return <ParseEmojis content={children.toString()} emojis={emojis} />
return (
<ParseEmojis
content={children.toString()}
emojis={emojis}
size={size}
/>
)
} else {
return null
}
@ -171,14 +178,15 @@ const ParseHTML: React.FC<Props> = ({
({ children }) => {
const lineHeight = StyleConstants.Font.LineHeight[size]
const [lines, setLines] = useState<number | undefined>(undefined)
const [heightOriginal, setHeightOriginal] = useState<number>()
const [heightTruncated, setHeightTruncated] = useState<number>()
const [allowExpand, setAllowExpand] = useState(false)
const [showAllText, setShowAllText] = useState(false)
const [expandAllow, setExpandAllow] = useState(false)
const [expanded, setExpanded] = useState(false)
const viewHeight = useDerivedValue(() => {
if (allowExpand) {
if (showAllText) {
if (expandAllow) {
if (expanded) {
return heightOriginal as number
} else {
return heightTruncated as number
@ -186,49 +194,29 @@ const ParseHTML: React.FC<Props> = ({
} else {
return heightOriginal as number
}
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
}, [heightOriginal, heightTruncated, expandAllow, expanded])
const ViewHeight = useAnimatedStyle(() => {
return {
height: allowExpand
? showAllText
height: expandAllow
? expanded
? withTiming(viewHeight.value)
: withTiming(viewHeight.value)
: undefined
: viewHeight.value
}
})
const calNumberOfLines = useMemo(() => {
if (numberOfLines === 0) {
// For spoilers without calculation
return showAllText ? undefined : 1
} else {
if (heightOriginal) {
if (!heightTruncated) {
return numberOfLines
} else {
return undefined
}
} else {
return undefined
}
}
}, [heightOriginal, heightTruncated, allowExpand, showAllText])
const onLayout = useCallback(
({ nativeEvent }) => {
if (numberOfLines === 0) {
// For spoilers without calculation
setAllowExpand(true)
if (!heightOriginal) {
setHeightOriginal(nativeEvent.layout.height)
setLines(numberOfLines === 0 ? 1 : numberOfLines)
} else {
if (!heightOriginal) {
setHeightOriginal(nativeEvent.layout.height)
if (!heightTruncated) {
setHeightTruncated(nativeEvent.layout.height)
setLines(undefined)
} else {
if (!heightTruncated) {
setHeightTruncated(nativeEvent.layout.height)
} else {
if (heightOriginal > heightTruncated) {
setAllowExpand(true)
}
if (heightOriginal > heightTruncated) {
setExpandAllow(true)
}
}
}
@ -239,21 +227,21 @@ const ParseHTML: React.FC<Props> = ({
return (
<View>
<Animated.View style={[ViewHeight, { overflow: 'hidden' }]}>
<Text
<Animated.Text
children={children}
onLayout={onLayout}
numberOfLines={lines}
style={{
...StyleConstants.FontStyle[size],
color: theme.primary,
height: allowExpand ? heightOriginal : undefined
height: expandAllow ? heightOriginal : undefined
}}
children={children}
numberOfLines={calNumberOfLines}
onLayout={onLayout}
/>
</Animated.View>
{allowExpand ? (
{expandAllow ? (
<Pressable
onPress={() => setShowAllText(!showAllText)}
style={{ marginTop: showAllText ? 0 : -lineHeight * 1.25 }}
onPress={() => setExpanded(!expanded)}
style={{ marginTop: expanded ? 0 : -lineHeight * 1.25 }}
>
<LinearGradient
colors={[
@ -273,7 +261,7 @@ const ParseHTML: React.FC<Props> = ({
color: theme.primary
}}
>
{`${showAllText ? '折叠' : '展开'}${expandHint}`}
{`${expanded ? '折叠' : '展开'}${expandHint}`}
</Text>
</LinearGradient>
</Pressable>

View File

@ -32,7 +32,7 @@ const RelationshipIncoming: React.FC<Props> = ({ id }) => {
onSuccess: ({ body }) => {
haptics('Success')
queryClient.setQueryData(relationshipQueryKey, body)
queryClient.invalidateQueries(['Notifications', {}])
queryClient.refetchQueries(['Notifications'])
},
onError: (err: any, { type }) => {
haptics('Error')

View File

@ -174,7 +174,23 @@ const Compose: React.FC<Props> = ({ route: { params }, navigation }) => {
composePost(params, composeState)
.then(() => {
haptics('Success')
queryClient.invalidateQueries(['Following'])
queryClient.invalidateQueries(['Following', {}])
if (
params?.type &&
(params.type === 'reply' || params.type === 'conversation')
) {
queryClient.invalidateQueries(
[
'Toot',
{
toot: params.incomingStatus.reblog
? params.incomingStatus.reblog.id
: params.incomingStatus.id
}
],
{ exact: true, active: true }
)
}
navigation.goBack()
})
.catch(() => {

View File

@ -141,19 +141,18 @@ const ComposeAttachments: React.FC = () => {
]}
>
<Chase
size={StyleConstants.Font.Size.L * 2}
size={StyleConstants.Font.Size.L * 1.5}
color={theme.primaryOverlay}
/>
</View>
) : (
<>
<View style={styles.actions}>
<Button
type='icon'
content='X'
spacing='M'
round
overlay
style={styles.delete}
onPress={() => {
layoutAnimation()
composeDispatch({
@ -169,14 +168,13 @@ const ComposeAttachments: React.FC = () => {
spacing='M'
round
overlay
style={styles.edit}
onPress={() =>
navigation.navigate('Screen-Shared-Compose-EditAttachment', {
index
})
}
/>
</>
</View>
)}
</View>
)
@ -294,15 +292,12 @@ const styles = StyleSheet.create({
justifyContent: 'center',
alignItems: 'center'
},
delete: {
position: 'absolute',
top: StyleConstants.Spacing.S,
right: StyleConstants.Spacing.S
},
edit: {
position: 'absolute',
bottom: StyleConstants.Spacing.S,
right: StyleConstants.Spacing.S
actions: {
...StyleSheet.absoluteFillObject,
justifyContent: 'space-between',
alignContent: 'flex-end',
alignItems: 'flex-end',
padding: StyleConstants.Spacing.S
}
})

View File

@ -29,10 +29,16 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
const theAttachmentRemote = composeState.attachments.uploads[index].remote!
const theAttachmentLocal = composeState.attachments.uploads[index].local!
const imageWidthBase =
theAttachmentRemote.meta?.original?.aspect < 1
? Dimensions.get('screen').width *
theAttachmentRemote.meta?.original?.aspect
: Dimensions.get('screen').width
const padding = (Dimensions.get('screen').width - imageWidthBase) / 2
const imageDimensionis = {
width: Dimensions.get('screen').width,
width: imageWidthBase,
height:
Dimensions.get('screen').width /
imageWidthBase /
(theAttachmentRemote as Mastodon.AttachmentImage).meta?.original?.aspect!
}
@ -75,8 +81,14 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
{
translateX: interpolate(
panX.value,
[-imageDimensionis.width / 2, imageDimensionis.width / 2],
[-imageDimensionis.width / 2, imageDimensionis.width / 2],
[
-imageDimensionis.width / 2 + padding,
imageDimensionis.width / 2 + padding
],
[
-imageDimensionis.width / 2 + padding,
imageDimensionis.width / 2 + padding
],
Extrapolate.CLAMP
)
},
@ -94,7 +106,7 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
return (
<>
<View style={{ overflow: 'hidden' }}>
<View style={{ overflow: 'hidden', flex: 1, alignItems: 'center' }}>
<Image
style={{
width: imageDimensionis.width,
@ -126,13 +138,13 @@ const ComposeEditAttachmentImage: React.FC<Props> = ({ index, focus }) => {
/>
<G transform='translate(967, 967)'>
<Circle
stroke={theme.background}
stroke={theme.primaryOverlay}
strokeWidth='2'
cx='33'
cy='33'
r='33'
/>
<Circle fill={theme.background} cx='33' cy='33' r='2' />
<Circle fill={theme.primaryOverlay} cx='33' cy='33' r='2' />
</G>
</G>
</G>

View File

@ -38,10 +38,19 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
return <ComposeEditAttachmentImage index={index} focus={focus} />
case 'video':
case 'gifv':
const video = composeState.attachments.uploads[index]
return (
<AttachmentVideo
sensitiveShown={false}
video={theAttachment as Mastodon.AttachmentVideo}
video={
video.local
? ({
url: video.local.uri,
preview_url: video.local.local_thumbnail,
blurhash: video.remote?.blurhash
} as Mastodon.AttachmentVideo)
: (video.remote! as Mastodon.AttachmentVideo)
}
/>
)
}
@ -56,7 +65,6 @@ const ComposeEditAttachmentRoot: React.FC<Props> = ({
</Text>
<TextInput
autoFocus
style={[styles.altTextInput, { borderColor: theme.border }]}
autoCapitalize='none'
autoCorrect={false}

View File

@ -1,5 +1,6 @@
import client from '@api/client'
import * as ImagePicker from 'expo-image-picker'
import * as Crypto from 'expo-crypto'
import { ImageInfo } from 'expo-image-picker/build/ImagePicker.types'
import * as VideoThumbnails from 'expo-video-thumbnails'
import { Dispatch } from 'react'
@ -11,13 +12,17 @@ export interface Props {
}
const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
const uploadAttachment = (result: ImageInfo) => {
const uploadAttachment = async (result: ImageInfo) => {
const hash = await Crypto.digestStringAsync(
Crypto.CryptoDigestAlgorithm.SHA256,
result.uri + Math.random()
)
switch (result.type) {
case 'image':
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...result, local_thumbnail: result.uri },
local: { ...result, local_thumbnail: result.uri, hash },
uploading: true
}
})
@ -28,7 +33,7 @@ const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: { ...result, local_thumbnail: uri },
local: { ...result, local_thumbnail: uri, hash },
uploading: true
}
})
@ -37,7 +42,7 @@ const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: result,
local: { ...result, hash },
uploading: true
}
})
@ -47,7 +52,7 @@ const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
composeDispatch({
type: 'attachment/upload/start',
payload: {
local: result,
local: { ...result, hash },
uploading: true
}
})
@ -62,7 +67,7 @@ const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
type: 'image/jpeg/jpg'
})
client({
return client({
method: 'post',
instance: 'local',
url: 'media',
@ -74,25 +79,30 @@ const addAttachment = async ({ composeDispatch }: Props): Promise<any> => {
type: 'attachment/upload/end',
payload: { remote: body, local: result }
})
return Promise.resolve()
} else {
composeDispatch({
type: 'attachment/upload/fail',
payload: hash
})
Alert.alert('上传失败', '', [
{
text: '返回重试',
onPress: () => {}
}
])
return Promise.reject()
}
})
.catch(() => {
composeDispatch({
type: 'attachment/upload/fail',
payload: hash
})
Alert.alert('上传失败', '', [
{
text: '返回重试',
onPress: () => {}
}
])
return Promise.reject()
})
}

View File

@ -40,7 +40,7 @@ const composePost = async (
formData.append('visibility', composeState.visibility)
const res = await client({
return client({
method: 'post',
instance: 'local',
url: 'statuses',
@ -63,12 +63,6 @@ const composePost = async (
},
body: formData
})
if (res.body.id) {
return Promise.resolve()
} else {
return Promise.resolve()
}
}
export default composePost

View File

@ -40,6 +40,16 @@ const composeReducer = (
)
}
}
case 'attachment/upload/fail':
return {
...state,
attachments: {
...state.attachments,
uploads: state.attachments.uploads.filter(
upload => upload.local.hash !== action.payload
)
}
}
case 'attachment/delete':
return {
...state,

View File

@ -1,6 +1,6 @@
export type ExtendedAttachment = {
remote?: Mastodon.Attachment
local?: ImageInfo & { local_thumbnail?: string }
local?: ImageInfo & { local_thumbnail?: string; hash?: string }
uploading?: boolean
}
@ -94,6 +94,10 @@ export type ComposeAction =
type: 'attachment/upload/end'
payload: { remote: Mastodon.Attachment; local: ImageInfo }
}
| {
type: 'attachment/upload/fail'
payload: ExtendedAttachment['local']['hash']
}
| {
type: 'attachment/delete'
payload: NonNullable<ExtendedAttachment['remote']>['id']

View File

@ -9,7 +9,7 @@ export const StyleConstants = {
FontStyle: {
S: { fontSize: 14, lineHeight: 18 },
M: { fontSize: 16, lineHeight: 22 },
L: { fontSize: 20, lineHeight: 30 }
L: { fontSize: 20, lineHeight: 26 }
},
Spacing: {