mirror of
https://github.com/tooot-app/app
synced 2025-03-13 01:50:08 +01:00
Editing can update media
This commit is contained in:
parent
d4f91a5756
commit
f93d6f7db8
@ -1,6 +1,6 @@
|
|||||||
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
import { useAccessibility } from '@utils/accessibility/AccessibilityManager'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
AccessibilityProps,
|
AccessibilityProps,
|
||||||
Image,
|
Image,
|
||||||
@ -39,125 +39,103 @@ export interface Props {
|
|||||||
>
|
>
|
||||||
}
|
}
|
||||||
|
|
||||||
const GracefullyImage = React.memo(
|
const GracefullyImage = ({
|
||||||
({
|
accessibilityLabel,
|
||||||
accessibilityLabel,
|
accessibilityHint,
|
||||||
accessibilityHint,
|
hidden = false,
|
||||||
hidden = false,
|
uri,
|
||||||
uri,
|
blurhash,
|
||||||
blurhash,
|
dimension,
|
||||||
dimension,
|
onPress,
|
||||||
onPress,
|
style,
|
||||||
style,
|
imageStyle,
|
||||||
imageStyle,
|
setImageDimensions
|
||||||
setImageDimensions
|
}: Props) => {
|
||||||
}: Props) => {
|
const { reduceMotionEnabled } = useAccessibility()
|
||||||
const { reduceMotionEnabled } = useAccessibility()
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
const [originalFailed, setOriginalFailed] = useState(false)
|
||||||
const [originalFailed, setOriginalFailed] = useState(false)
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
const [imageLoaded, setImageLoaded] = useState(false)
|
|
||||||
|
|
||||||
const source = useMemo(() => {
|
const source = originalFailed
|
||||||
if (originalFailed) {
|
? { uri: uri.remote || undefined }
|
||||||
return { uri: uri.remote || undefined }
|
: {
|
||||||
} else {
|
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
||||||
return {
|
|
||||||
uri: reduceMotionEnabled && uri.static ? uri.static : uri.original
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [originalFailed])
|
|
||||||
|
|
||||||
const onLoad = useCallback(() => {
|
const onLoad = () => {
|
||||||
setImageLoaded(true)
|
setImageLoaded(true)
|
||||||
if (setImageDimensions && source.uri) {
|
if (setImageDimensions && source.uri) {
|
||||||
Image.getSize(source.uri, (width, height) =>
|
Image.getSize(source.uri, (width, height) =>
|
||||||
setImageDimensions({ width, height })
|
setImageDimensions({ width, height })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const onError = () => {
|
||||||
|
if (!originalFailed) {
|
||||||
|
setOriginalFailed(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const blurhashView = useMemo(() => {
|
||||||
|
if (hidden || !imageLoaded) {
|
||||||
|
if (blurhash) {
|
||||||
|
return (
|
||||||
|
<Blurhash
|
||||||
|
decodeAsync
|
||||||
|
blurhash={blurhash}
|
||||||
|
style={styles.placeholder}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
}, [source.uri])
|
return (
|
||||||
const onError = useCallback(() => {
|
<View
|
||||||
if (!originalFailed) {
|
|
||||||
setOriginalFailed(true)
|
|
||||||
}
|
|
||||||
}, [originalFailed])
|
|
||||||
|
|
||||||
const previewView = useMemo(
|
|
||||||
() =>
|
|
||||||
uri.preview && !imageLoaded ? (
|
|
||||||
<Image
|
|
||||||
fadeDuration={0}
|
|
||||||
source={{ uri: uri.preview }}
|
|
||||||
style={[
|
style={[
|
||||||
styles.placeholder,
|
styles.placeholder,
|
||||||
{ backgroundColor: colors.shimmerDefault }
|
{ backgroundColor: colors.shimmerDefault }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
) : null,
|
)
|
||||||
[]
|
}
|
||||||
)
|
} else {
|
||||||
const originalView = useMemo(
|
return null
|
||||||
() => (
|
}
|
||||||
|
}, [hidden, imageLoaded])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
{...(onPress
|
||||||
|
? { accessibilityRole: 'imagebutton' }
|
||||||
|
: { accessibilityRole: 'image' })}
|
||||||
|
accessibilityLabel={accessibilityLabel}
|
||||||
|
accessibilityHint={accessibilityHint}
|
||||||
|
style={[style, dimension, { backgroundColor: colors.shimmerDefault }]}
|
||||||
|
{...(onPress
|
||||||
|
? hidden
|
||||||
|
? { disabled: true }
|
||||||
|
: { onPress }
|
||||||
|
: { disabled: true })}
|
||||||
|
>
|
||||||
|
{uri.preview && !imageLoaded ? (
|
||||||
<Image
|
<Image
|
||||||
fadeDuration={0}
|
fadeDuration={0}
|
||||||
source={source}
|
source={{ uri: uri.preview }}
|
||||||
style={[{ flex: 1 }, imageStyle]}
|
style={[
|
||||||
onLoad={onLoad}
|
styles.placeholder,
|
||||||
onError={onError}
|
{ backgroundColor: colors.shimmerDefault }
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
),
|
) : null}
|
||||||
[source]
|
<Image
|
||||||
)
|
fadeDuration={0}
|
||||||
const blurhashView = useMemo(() => {
|
source={source}
|
||||||
if (hidden || !imageLoaded) {
|
style={[{ flex: 1 }, imageStyle]}
|
||||||
if (blurhash) {
|
onLoad={onLoad}
|
||||||
return (
|
onError={onError}
|
||||||
<Blurhash
|
/>
|
||||||
decodeAsync
|
{blurhashView}
|
||||||
blurhash={blurhash}
|
</Pressable>
|
||||||
style={styles.placeholder}
|
)
|
||||||
/>
|
}
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
styles.placeholder,
|
|
||||||
{ backgroundColor: colors.shimmerDefault }
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}, [hidden, imageLoaded])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
{...(onPress
|
|
||||||
? { accessibilityRole: 'imagebutton' }
|
|
||||||
: { accessibilityRole: 'image' })}
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
accessibilityHint={accessibilityHint}
|
|
||||||
style={[style, dimension, { backgroundColor: colors.shimmerDefault }]}
|
|
||||||
{...(onPress
|
|
||||||
? hidden
|
|
||||||
? { disabled: true }
|
|
||||||
: { onPress }
|
|
||||||
: { disabled: true })}
|
|
||||||
>
|
|
||||||
{previewView}
|
|
||||||
{originalView}
|
|
||||||
{blurhashView}
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
(prev, next) =>
|
|
||||||
prev.hidden === next.hidden &&
|
|
||||||
prev.uri.preview === next.uri.preview &&
|
|
||||||
prev.uri.original === next.uri.original &&
|
|
||||||
prev.uri.remote === next.uri.remote
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
placeholder: {
|
placeholder: {
|
||||||
|
@ -10,10 +10,9 @@ import { StackNavigationProp } from '@react-navigation/stack'
|
|||||||
import { RootStackParamList } from '@utils/navigation/navigators'
|
import { RootStackParamList } from '@utils/navigation/navigators'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import layoutAnimation from '@utils/styles/layoutAnimation'
|
import layoutAnimation from '@utils/styles/layoutAnimation'
|
||||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
import React, { useRef, useState } from 'react'
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
status: Pick<Mastodon.Status, 'media_attachments' | 'sensitive'>
|
||||||
@ -24,24 +23,13 @@ const TimelineAttachment = React.memo(
|
|||||||
const { t } = useTranslation('componentTimeline')
|
const { t } = useTranslation('componentTimeline')
|
||||||
|
|
||||||
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
const [sensitiveShown, setSensitiveShown] = useState(status.sensitive)
|
||||||
const onPressBlurView = useCallback(() => {
|
|
||||||
analytics('timeline_shared_attachment_blurview_press_show')
|
|
||||||
layoutAnimation()
|
|
||||||
setSensitiveShown(false)
|
|
||||||
haptics('Light')
|
|
||||||
}, [])
|
|
||||||
const onPressShow = useCallback(() => {
|
|
||||||
analytics('timeline_shared_attachment_blurview_press_hide')
|
|
||||||
setSensitiveShown(true)
|
|
||||||
haptics('Light')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const imageUrls = useRef<
|
const imageUrls = useRef<
|
||||||
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
RootStackParamList['Screen-ImagesViewer']['imageUrls']
|
||||||
>([])
|
>([])
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
useEffect(() => {
|
const navigateToImagesViewer = (id: string) => {
|
||||||
status.media_attachments.forEach((attachment, index) => {
|
status.media_attachments.forEach(attachment => {
|
||||||
switch (attachment.type) {
|
switch (attachment.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
imageUrls.current.push({
|
imageUrls.current.push({
|
||||||
@ -55,117 +43,136 @@ const TimelineAttachment = React.memo(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [])
|
|
||||||
const navigateToImagesViewer = (id: string) =>
|
|
||||||
navigation.navigate('Screen-ImagesViewer', {
|
navigation.navigate('Screen-ImagesViewer', {
|
||||||
imageUrls: imageUrls.current,
|
imageUrls: imageUrls.current,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
const attachments = useMemo(
|
}
|
||||||
() =>
|
|
||||||
status.media_attachments.map((attachment, index) => {
|
return (
|
||||||
switch (attachment.type) {
|
<View>
|
||||||
case 'image':
|
<View
|
||||||
return (
|
style={{
|
||||||
<AttachmentImage
|
marginTop: StyleConstants.Spacing.S,
|
||||||
key={index}
|
flex: 1,
|
||||||
total={status.media_attachments.length}
|
flexDirection: 'row',
|
||||||
index={index}
|
flexWrap: 'wrap',
|
||||||
sensitiveShown={sensitiveShown}
|
justifyContent: 'center',
|
||||||
image={attachment}
|
alignContent: 'stretch'
|
||||||
navigateToImagesViewer={navigateToImagesViewer}
|
}}
|
||||||
/>
|
>
|
||||||
)
|
{status.media_attachments.map((attachment, index) => {
|
||||||
case 'video':
|
switch (attachment.type) {
|
||||||
return (
|
case 'image':
|
||||||
<AttachmentVideo
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
video={attachment}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'gifv':
|
|
||||||
return (
|
|
||||||
<AttachmentVideo
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
video={attachment}
|
|
||||||
gifv
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
case 'audio':
|
|
||||||
return (
|
|
||||||
<AttachmentAudio
|
|
||||||
key={index}
|
|
||||||
total={status.media_attachments.length}
|
|
||||||
index={index}
|
|
||||||
sensitiveShown={sensitiveShown}
|
|
||||||
audio={attachment}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
if (
|
|
||||||
attachment.preview_url?.endsWith('.jpg') ||
|
|
||||||
attachment.preview_url?.endsWith('.jpeg') ||
|
|
||||||
attachment.preview_url?.endsWith('.png') ||
|
|
||||||
attachment.preview_url?.endsWith('.gif') ||
|
|
||||||
attachment.remote_url?.endsWith('.jpg') ||
|
|
||||||
attachment.remote_url?.endsWith('.jpeg') ||
|
|
||||||
attachment.remote_url?.endsWith('.png') ||
|
|
||||||
attachment.remote_url?.endsWith('.gif')
|
|
||||||
) {
|
|
||||||
imageUrls.current.push({
|
|
||||||
id: attachment.id,
|
|
||||||
preview_url: attachment.preview_url,
|
|
||||||
url: attachment.url,
|
|
||||||
remote_url: attachment.remote_url,
|
|
||||||
blurhash: attachment.blurhash,
|
|
||||||
width: attachment.meta?.original?.width,
|
|
||||||
height: attachment.meta?.original?.height
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<AttachmentImage
|
<AttachmentImage
|
||||||
key={index}
|
key={index}
|
||||||
total={status.media_attachments.length}
|
total={status.media_attachments.length}
|
||||||
index={index}
|
index={index}
|
||||||
sensitiveShown={sensitiveShown}
|
sensitiveShown={sensitiveShown}
|
||||||
// @ts-ignore
|
|
||||||
image={attachment}
|
image={attachment}
|
||||||
navigateToImagesViewer={navigateToImagesViewer}
|
navigateToImagesViewer={navigateToImagesViewer}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
case 'video':
|
||||||
return (
|
return (
|
||||||
<AttachmentUnsupported
|
<AttachmentVideo
|
||||||
key={index}
|
key={index}
|
||||||
total={status.media_attachments.length}
|
total={status.media_attachments.length}
|
||||||
index={index}
|
index={index}
|
||||||
sensitiveShown={sensitiveShown}
|
sensitiveShown={sensitiveShown}
|
||||||
attachment={attachment}
|
video={attachment}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
case 'gifv':
|
||||||
}
|
return (
|
||||||
}),
|
<AttachmentVideo
|
||||||
[sensitiveShown]
|
key={index}
|
||||||
)
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
return (
|
sensitiveShown={sensitiveShown}
|
||||||
<View>
|
video={attachment}
|
||||||
<View style={styles.container} children={attachments} />
|
gifv
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 'audio':
|
||||||
|
return (
|
||||||
|
<AttachmentAudio
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
audio={attachment}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
if (
|
||||||
|
attachment.preview_url?.endsWith('.jpg') ||
|
||||||
|
attachment.preview_url?.endsWith('.jpeg') ||
|
||||||
|
attachment.preview_url?.endsWith('.png') ||
|
||||||
|
attachment.preview_url?.endsWith('.gif') ||
|
||||||
|
attachment.remote_url?.endsWith('.jpg') ||
|
||||||
|
attachment.remote_url?.endsWith('.jpeg') ||
|
||||||
|
attachment.remote_url?.endsWith('.png') ||
|
||||||
|
attachment.remote_url?.endsWith('.gif')
|
||||||
|
) {
|
||||||
|
imageUrls.current.push({
|
||||||
|
id: attachment.id,
|
||||||
|
preview_url: attachment.preview_url,
|
||||||
|
url: attachment.url,
|
||||||
|
remote_url: attachment.remote_url,
|
||||||
|
blurhash: attachment.blurhash,
|
||||||
|
width: attachment.meta?.original?.width,
|
||||||
|
height: attachment.meta?.original?.height
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<AttachmentImage
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
// @ts-ignore
|
||||||
|
image={attachment}
|
||||||
|
navigateToImagesViewer={navigateToImagesViewer}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<AttachmentUnsupported
|
||||||
|
key={index}
|
||||||
|
total={status.media_attachments.length}
|
||||||
|
index={index}
|
||||||
|
sensitiveShown={sensitiveShown}
|
||||||
|
attachment={attachment}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</View>
|
||||||
|
|
||||||
{status.sensitive &&
|
{status.sensitive &&
|
||||||
(sensitiveShown ? (
|
(sensitiveShown ? (
|
||||||
<Pressable style={styles.sensitiveBlur}>
|
<Pressable
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
type='text'
|
type='text'
|
||||||
content={t('shared.attachment.sensitive.button')}
|
content={t('shared.attachment.sensitive.button')}
|
||||||
overlay
|
overlay
|
||||||
onPress={onPressBlurView}
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_attachment_blurview_press_show')
|
||||||
|
layoutAnimation()
|
||||||
|
setSensitiveShown(false)
|
||||||
|
haptics('Light')
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
) : (
|
) : (
|
||||||
@ -174,7 +181,11 @@ const TimelineAttachment = React.memo(
|
|||||||
content='EyeOff'
|
content='EyeOff'
|
||||||
round
|
round
|
||||||
overlay
|
overlay
|
||||||
onPress={onPressShow}
|
onPress={() => {
|
||||||
|
analytics('timeline_shared_attachment_blurview_press_hide')
|
||||||
|
setSensitiveShown(true)
|
||||||
|
haptics('Light')
|
||||||
|
}}
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: StyleConstants.Spacing.S * 2,
|
top: StyleConstants.Spacing.S * 2,
|
||||||
@ -185,33 +196,28 @@ const TimelineAttachment = React.memo(
|
|||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
() => true
|
(prev, next) => {
|
||||||
|
let isEqual = true
|
||||||
|
|
||||||
|
if (
|
||||||
|
prev.status.media_attachments.length !==
|
||||||
|
next.status.media_attachments.length
|
||||||
|
) {
|
||||||
|
isEqual = false
|
||||||
|
return isEqual
|
||||||
|
}
|
||||||
|
|
||||||
|
prev.status.media_attachments.forEach((attachment, index) => {
|
||||||
|
if (
|
||||||
|
attachment.preview_url !==
|
||||||
|
next.status.media_attachments[index].preview_url
|
||||||
|
) {
|
||||||
|
isEqual = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return isEqual
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
marginTop: StyleConstants.Spacing.S,
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignContent: 'stretch'
|
|
||||||
},
|
|
||||||
sensitiveBlur: {
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center'
|
|
||||||
},
|
|
||||||
sensitiveBlurButton: {
|
|
||||||
padding: StyleConstants.Spacing.S,
|
|
||||||
borderRadius: 6
|
|
||||||
},
|
|
||||||
sensitiveText: {
|
|
||||||
...StyleConstants.FontStyle.M
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default TimelineAttachment
|
export default TimelineAttachment
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import analytics from '@components/analytics'
|
import analytics from '@components/analytics'
|
||||||
import GracefullyImage from '@components/GracefullyImage'
|
import GracefullyImage from '@components/GracefullyImage'
|
||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import React, { useCallback } from 'react'
|
import React from 'react'
|
||||||
import { StyleSheet, View } from 'react-native'
|
import { View } from 'react-native'
|
||||||
import attachmentAspectRatio from './aspectRatio'
|
import attachmentAspectRatio from './aspectRatio'
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
@ -13,44 +13,43 @@ export interface Props {
|
|||||||
navigateToImagesViewer: (imageIndex: string) => void
|
navigateToImagesViewer: (imageIndex: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttachmentImage = React.memo(
|
const AttachmentImage = ({
|
||||||
({ total, index, sensitiveShown, image, navigateToImagesViewer }: Props) => {
|
total,
|
||||||
const onPress = useCallback(() => {
|
index,
|
||||||
analytics('timeline_shared_attachment_image_press', { id: image.id })
|
sensitiveShown,
|
||||||
navigateToImagesViewer(image.id)
|
image,
|
||||||
}, [])
|
navigateToImagesViewer
|
||||||
|
}: Props) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View
|
||||||
<GracefullyImage
|
style={{
|
||||||
accessibilityLabel={image.description}
|
flex: 1,
|
||||||
hidden={sensitiveShown}
|
flexBasis: '50%',
|
||||||
uri={{ original: image.preview_url, remote: image.remote_url }}
|
padding: StyleConstants.Spacing.XS / 2
|
||||||
blurhash={image.blurhash}
|
}}
|
||||||
onPress={onPress}
|
>
|
||||||
style={{
|
<GracefullyImage
|
||||||
aspectRatio:
|
accessibilityLabel={image.description}
|
||||||
total > 1 ||
|
hidden={sensitiveShown}
|
||||||
!image.meta?.original?.width ||
|
uri={{ original: image.preview_url, remote: image.remote_url }}
|
||||||
!image.meta?.original?.height
|
blurhash={image.blurhash}
|
||||||
? attachmentAspectRatio({ total, index })
|
onPress={() => {
|
||||||
: image.meta.original.height / image.meta.original.width > 1
|
analytics('timeline_shared_attachment_image_press', { id: image.id })
|
||||||
? 1
|
navigateToImagesViewer(image.id)
|
||||||
: image.meta.original.width / image.meta.original.height
|
}}
|
||||||
}}
|
style={{
|
||||||
/>
|
aspectRatio:
|
||||||
</View>
|
total > 1 ||
|
||||||
)
|
!image.meta?.original?.width ||
|
||||||
},
|
!image.meta?.original?.height
|
||||||
(prev, next) => prev.sensitiveShown === next.sensitiveShown
|
? attachmentAspectRatio({ total, index })
|
||||||
)
|
: image.meta.original.height / image.meta.original.width > 1
|
||||||
|
? 1
|
||||||
const styles = StyleSheet.create({
|
: image.meta.original.width / image.meta.original.height
|
||||||
base: {
|
}}
|
||||||
flex: 1,
|
/>
|
||||||
flexBasis: '50%',
|
</View>
|
||||||
padding: StyleConstants.Spacing.XS / 2
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
export default AttachmentImage
|
export default AttachmentImage
|
||||||
|
@ -10,142 +10,116 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native'
|
import { Pressable, Text, View } from 'react-native'
|
||||||
import { useQueryClient } from 'react-query'
|
import { useQueryClient } from 'react-query'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
import HeaderSharedMuted from './HeaderShared/Muted'
|
import HeaderSharedMuted from './HeaderShared/Muted'
|
||||||
|
|
||||||
const Names = React.memo(
|
const Names = ({ accounts }: { accounts: Mastodon.Account[] }) => {
|
||||||
({ accounts }: { accounts: Mastodon.Account[] }) => {
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { t } = useTranslation('componentTimeline')
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={[styles.namesLeading, { color: colors.secondary }]}
|
style={{ ...StyleConstants.FontStyle.M, color: colors.secondary }}
|
||||||
>
|
>
|
||||||
<Text>{t('shared.header.conversation.withAccounts')}</Text>
|
<Text>{t('shared.header.conversation.withAccounts')}</Text>
|
||||||
{accounts.map((account, index) => (
|
{accounts.map((account, index) => (
|
||||||
<Text key={account.id} numberOfLines={1}>
|
<Text key={account.id} numberOfLines={1}>
|
||||||
{index !== 0 ? t('common:separator') : undefined}
|
{index !== 0 ? t('common:separator') : undefined}
|
||||||
<ParseEmojis
|
<ParseEmojis
|
||||||
content={account.display_name || account.username}
|
content={account.display_name || account.username}
|
||||||
emojis={account.emojis}
|
emojis={account.emojis}
|
||||||
fontBold
|
fontBold
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
queryKey: QueryKeyTimeline
|
queryKey: QueryKeyTimeline
|
||||||
conversation: Mastodon.Conversation
|
conversation: Mastodon.Conversation
|
||||||
}
|
}
|
||||||
|
|
||||||
const HeaderConversation = React.memo(
|
const HeaderConversation = ({ queryKey, conversation }: Props) => {
|
||||||
({ queryKey, conversation }: Props) => {
|
const { colors, theme } = useTheme()
|
||||||
const { colors, theme } = useTheme()
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { t } = useTranslation('componentTimeline')
|
|
||||||
|
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const mutation = useTimelineMutation({
|
const mutation = useTimelineMutation({
|
||||||
onMutate: true,
|
onMutate: true,
|
||||||
onError: (err: any, _, oldData) => {
|
onError: (err: any, _, oldData) => {
|
||||||
displayMessage({
|
displayMessage({
|
||||||
theme,
|
theme,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: t('common:message.error.message', {
|
message: t('common:message.error.message', {
|
||||||
function: t(`shared.header.conversation.delete.function`)
|
function: t(`shared.header.conversation.delete.function`)
|
||||||
}),
|
}),
|
||||||
...(err.status &&
|
...(err.status &&
|
||||||
typeof err.status === 'number' &&
|
typeof err.status === 'number' &&
|
||||||
err.data &&
|
err.data &&
|
||||||
err.data.error &&
|
err.data.error &&
|
||||||
typeof err.data.error === 'string' && {
|
typeof err.data.error === 'string' && {
|
||||||
description: err.data.error
|
description: err.data.error
|
||||||
})
|
})
|
||||||
})
|
|
||||||
queryClient.setQueryData(queryKey, oldData)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const actionOnPress = useCallback(() => {
|
|
||||||
analytics('timeline_conversation_delete_press')
|
|
||||||
mutation.mutate({
|
|
||||||
type: 'deleteItem',
|
|
||||||
source: 'conversations',
|
|
||||||
queryKey,
|
|
||||||
id: conversation.id
|
|
||||||
})
|
})
|
||||||
}, [])
|
queryClient.setQueryData(queryKey, oldData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const actionChildren = useMemo(
|
const actionOnPress = useCallback(() => {
|
||||||
() => (
|
analytics('timeline_conversation_delete_press')
|
||||||
<Icon
|
mutation.mutate({
|
||||||
name='Trash'
|
type: 'deleteItem',
|
||||||
color={colors.secondary}
|
source: 'conversations',
|
||||||
size={StyleConstants.Font.Size.L}
|
queryKey,
|
||||||
/>
|
id: conversation.id
|
||||||
),
|
})
|
||||||
[]
|
}, [])
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
const actionChildren = useMemo(
|
||||||
<View style={styles.base}>
|
() => (
|
||||||
<View style={styles.nameAndMeta}>
|
<Icon
|
||||||
<Names accounts={conversation.accounts} />
|
name='Trash'
|
||||||
<View style={styles.meta}>
|
color={colors.secondary}
|
||||||
{conversation.last_status?.created_at ? (
|
size={StyleConstants.Font.Size.L}
|
||||||
<HeaderSharedCreated
|
/>
|
||||||
created_at={conversation.last_status?.created_at}
|
),
|
||||||
edited_at={conversation.last_status?.edited_at}
|
[]
|
||||||
/>
|
)
|
||||||
) : null}
|
|
||||||
<HeaderSharedMuted muted={conversation.last_status?.muted} />
|
return (
|
||||||
</View>
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
|
<View style={{ flex: 3 }}>
|
||||||
|
<Names accounts={conversation.accounts} />
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{conversation.last_status?.created_at ? (
|
||||||
|
<HeaderSharedCreated
|
||||||
|
created_at={conversation.last_status?.created_at}
|
||||||
|
edited_at={conversation.last_status?.edited_at}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<HeaderSharedMuted muted={conversation.last_status?.muted} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<Pressable
|
|
||||||
style={styles.action}
|
|
||||||
onPress={actionOnPress}
|
|
||||||
children={actionChildren}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
<Pressable
|
||||||
base: {
|
style={{ flex: 1, flexDirection: 'row', justifyContent: 'center' }}
|
||||||
flex: 1,
|
onPress={actionOnPress}
|
||||||
flexDirection: 'row'
|
children={actionChildren}
|
||||||
},
|
/>
|
||||||
nameAndMeta: {
|
</View>
|
||||||
flex: 3
|
)
|
||||||
},
|
}
|
||||||
meta: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: StyleConstants.Spacing.XS,
|
|
||||||
marginBottom: StyleConstants.Spacing.S
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
...StyleConstants.FontStyle.S
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'center'
|
|
||||||
},
|
|
||||||
namesLeading: {
|
|
||||||
...StyleConstants.FontStyle.M
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default HeaderConversation
|
export default HeaderConversation
|
||||||
|
@ -7,7 +7,7 @@ import { StyleConstants } from '@utils/styles/constants'
|
|||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
@ -20,77 +20,61 @@ export interface Props {
|
|||||||
status: Mastodon.Status
|
status: Mastodon.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineHeaderDefault = React.memo(
|
const TimelineHeaderDefault = ({ queryKey, rootQueryKey, status }: Props) => {
|
||||||
({ queryKey, rootQueryKey, status }: Props) => {
|
const { t } = useTranslation('componentTimeline')
|
||||||
const { t } = useTranslation('componentTimeline')
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.base}>
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
<View style={styles.accountAndMeta}>
|
<View style={{ flex: 5 }}>
|
||||||
<HeaderSharedAccount account={status.account} />
|
<HeaderSharedAccount account={status.account} />
|
||||||
<View style={styles.meta}>
|
<View
|
||||||
<HeaderSharedCreated
|
style={{
|
||||||
created_at={status.created_at}
|
flexDirection: 'row',
|
||||||
edited_at={status.edited_at}
|
alignItems: 'center',
|
||||||
/>
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
<HeaderSharedVisibility visibility={status.visibility} />
|
marginBottom: StyleConstants.Spacing.S
|
||||||
<HeaderSharedMuted muted={status.muted} />
|
}}
|
||||||
<HeaderSharedApplication application={status.application} />
|
>
|
||||||
</View>
|
<HeaderSharedCreated
|
||||||
</View>
|
created_at={status.created_at}
|
||||||
|
edited_at={status.edited_at}
|
||||||
{queryKey ? (
|
|
||||||
<Pressable
|
|
||||||
accessibilityHint={t('shared.header.actions.accessibilityHint')}
|
|
||||||
style={styles.action}
|
|
||||||
onPress={() =>
|
|
||||||
navigation.navigate('Screen-Actions', {
|
|
||||||
queryKey,
|
|
||||||
rootQueryKey,
|
|
||||||
status,
|
|
||||||
type: 'status'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
children={
|
|
||||||
<Icon
|
|
||||||
name='MoreHorizontal'
|
|
||||||
color={colors.secondary}
|
|
||||||
size={StyleConstants.Font.Size.L}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
<HeaderSharedVisibility visibility={status.visibility} />
|
||||||
|
<HeaderSharedMuted muted={status.muted} />
|
||||||
|
<HeaderSharedApplication application={status.application} />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
{queryKey ? (
|
||||||
base: {
|
<Pressable
|
||||||
flex: 1,
|
accessibilityHint={t('shared.header.actions.accessibilityHint')}
|
||||||
flexDirection: 'row'
|
style={{
|
||||||
},
|
flex: 1,
|
||||||
accountAndMeta: {
|
flexDirection: 'row',
|
||||||
flex: 5
|
justifyContent: 'center',
|
||||||
},
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
meta: {
|
}}
|
||||||
flexDirection: 'row',
|
onPress={() =>
|
||||||
alignItems: 'center',
|
navigation.navigate('Screen-Actions', {
|
||||||
marginTop: StyleConstants.Spacing.XS,
|
queryKey,
|
||||||
marginBottom: StyleConstants.Spacing.S
|
rootQueryKey,
|
||||||
},
|
status,
|
||||||
created_at: {
|
type: 'status'
|
||||||
...StyleConstants.FontStyle.S
|
})
|
||||||
},
|
}
|
||||||
action: {
|
children={
|
||||||
flex: 1,
|
<Icon
|
||||||
flexDirection: 'row',
|
name='MoreHorizontal'
|
||||||
justifyContent: 'center',
|
color={colors.secondary}
|
||||||
paddingBottom: StyleConstants.Spacing.S
|
size={StyleConstants.Font.Size.L}
|
||||||
}
|
/>
|
||||||
})
|
}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default TimelineHeaderDefault
|
export default TimelineHeaderDefault
|
||||||
|
@ -10,7 +10,7 @@ import { QueryKeyTimeline } from '@utils/queryHooks/timeline'
|
|||||||
import { StyleConstants } from '@utils/styles/constants'
|
import { StyleConstants } from '@utils/styles/constants'
|
||||||
import { useTheme } from '@utils/styles/ThemeManager'
|
import { useTheme } from '@utils/styles/ThemeManager'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Pressable, StyleSheet, View } from 'react-native'
|
import { Pressable, View } from 'react-native'
|
||||||
import HeaderSharedAccount from './HeaderShared/Account'
|
import HeaderSharedAccount from './HeaderShared/Account'
|
||||||
import HeaderSharedApplication from './HeaderShared/Application'
|
import HeaderSharedApplication from './HeaderShared/Application'
|
||||||
import HeaderSharedCreated from './HeaderShared/Created'
|
import HeaderSharedCreated from './HeaderShared/Created'
|
||||||
@ -22,115 +22,103 @@ export interface Props {
|
|||||||
notification: Mastodon.Notification
|
notification: Mastodon.Notification
|
||||||
}
|
}
|
||||||
|
|
||||||
const TimelineHeaderNotification = React.memo(
|
const TimelineHeaderNotification = ({ queryKey, notification }: Props) => {
|
||||||
({ queryKey, notification }: Props) => {
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>()
|
const { colors } = useTheme()
|
||||||
const { colors } = useTheme()
|
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
const actions = useMemo(() => {
|
||||||
switch (notification.type) {
|
switch (notification.type) {
|
||||||
case 'follow':
|
case 'follow':
|
||||||
return <RelationshipOutgoing id={notification.account.id} />
|
return <RelationshipOutgoing id={notification.account.id} />
|
||||||
case 'follow_request':
|
case 'follow_request':
|
||||||
return <RelationshipIncoming id={notification.account.id} />
|
return <RelationshipIncoming id={notification.account.id} />
|
||||||
default:
|
default:
|
||||||
if (notification.status) {
|
if (notification.status) {
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
paddingBottom: StyleConstants.Spacing.S
|
paddingBottom: StyleConstants.Spacing.S
|
||||||
}}
|
}}
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate('Screen-Actions', {
|
navigation.navigate('Screen-Actions', {
|
||||||
queryKey,
|
queryKey,
|
||||||
status: notification.status!,
|
status: notification.status!,
|
||||||
type: 'status'
|
type: 'status'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
children={
|
children={
|
||||||
<Icon
|
<Icon
|
||||||
name='MoreHorizontal'
|
name='MoreHorizontal'
|
||||||
color={colors.secondary}
|
color={colors.secondary}
|
||||||
size={StyleConstants.Font.Size.L}
|
size={StyleConstants.Font.Size.L}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [notification.type])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.base}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flex:
|
|
||||||
notification.type === 'follow' ||
|
|
||||||
notification.type === 'follow_request'
|
|
||||||
? 1
|
|
||||||
: 4
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HeaderSharedAccount
|
|
||||||
account={
|
|
||||||
notification.status
|
|
||||||
? notification.status.account
|
|
||||||
: notification.account
|
|
||||||
}
|
|
||||||
{...((notification.type === 'follow' ||
|
|
||||||
notification.type === 'follow_request') && { withoutName: true })}
|
|
||||||
/>
|
|
||||||
<View style={styles.meta}>
|
|
||||||
<HeaderSharedCreated
|
|
||||||
created_at={notification.created_at}
|
|
||||||
edited_at={notification.status?.edited_at}
|
|
||||||
/>
|
/>
|
||||||
{notification.status?.visibility ? (
|
)
|
||||||
<HeaderSharedVisibility
|
}
|
||||||
visibility={notification.status.visibility}
|
}
|
||||||
/>
|
}, [notification.type])
|
||||||
) : null}
|
|
||||||
<HeaderSharedMuted muted={notification.status?.muted} />
|
|
||||||
<HeaderSharedApplication
|
|
||||||
application={notification.status?.application}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View
|
return (
|
||||||
style={[
|
<View style={{ flex: 1, flexDirection: 'row' }}>
|
||||||
styles.relationship,
|
<View
|
||||||
|
style={{
|
||||||
|
flex:
|
||||||
notification.type === 'follow' ||
|
notification.type === 'follow' ||
|
||||||
notification.type === 'follow_request'
|
notification.type === 'follow_request'
|
||||||
? { flexShrink: 1 }
|
? 1
|
||||||
: { flex: 1 }
|
: 4
|
||||||
]}
|
}}
|
||||||
|
>
|
||||||
|
<HeaderSharedAccount
|
||||||
|
account={
|
||||||
|
notification.status
|
||||||
|
? notification.status.account
|
||||||
|
: notification.account
|
||||||
|
}
|
||||||
|
{...((notification.type === 'follow' ||
|
||||||
|
notification.type === 'follow_request') && { withoutName: true })}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: StyleConstants.Spacing.XS,
|
||||||
|
marginBottom: StyleConstants.Spacing.S
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{actions}
|
<HeaderSharedCreated
|
||||||
|
created_at={notification.created_at}
|
||||||
|
edited_at={notification.status?.edited_at}
|
||||||
|
/>
|
||||||
|
{notification.status?.visibility ? (
|
||||||
|
<HeaderSharedVisibility
|
||||||
|
visibility={notification.status.visibility}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<HeaderSharedMuted muted={notification.status?.muted} />
|
||||||
|
<HeaderSharedApplication
|
||||||
|
application={notification.status?.application}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
<View
|
||||||
base: {
|
style={[
|
||||||
flex: 1,
|
{ marginLeft: StyleConstants.Spacing.M },
|
||||||
flexDirection: 'row'
|
notification.type === 'follow' ||
|
||||||
},
|
notification.type === 'follow_request'
|
||||||
meta: {
|
? { flexShrink: 1 }
|
||||||
flexDirection: 'row',
|
: { flex: 1 }
|
||||||
alignItems: 'center',
|
]}
|
||||||
marginTop: StyleConstants.Spacing.XS,
|
>
|
||||||
marginBottom: StyleConstants.Spacing.S
|
{actions}
|
||||||
},
|
</View>
|
||||||
relationship: {
|
</View>
|
||||||
marginLeft: StyleConstants.Spacing.M
|
)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
export default TimelineHeaderNotification
|
export default TimelineHeaderNotification
|
||||||
|
@ -35,191 +35,189 @@ import ActionsNotificationsFilter from './Actions/NotificationsFilter'
|
|||||||
import ActionsShare from './Actions/Share'
|
import ActionsShare from './Actions/Share'
|
||||||
import ActionsStatus from './Actions/Status'
|
import ActionsStatus from './Actions/Status'
|
||||||
|
|
||||||
const ScreenActions = React.memo(
|
const ScreenActions = ({
|
||||||
({
|
route: { params },
|
||||||
route: { params },
|
navigation
|
||||||
navigation
|
}: RootStackScreenProps<'Screen-Actions'>) => {
|
||||||
}: RootStackScreenProps<'Screen-Actions'>) => {
|
const { t } = useTranslation()
|
||||||
const { t } = useTranslation()
|
|
||||||
|
|
||||||
const instanceAccount = useSelector(
|
const instanceAccount = useSelector(
|
||||||
getInstanceAccount,
|
getInstanceAccount,
|
||||||
(prev, next) => prev?.id === next?.id
|
(prev, next) => prev?.id === next?.id
|
||||||
)
|
)
|
||||||
let sameAccount = false
|
let sameAccount = false
|
||||||
|
switch (params.type) {
|
||||||
|
case 'status':
|
||||||
|
console.log('media length', params.status.media_attachments.length)
|
||||||
|
sameAccount = instanceAccount?.id === params.status.account.id
|
||||||
|
break
|
||||||
|
case 'account':
|
||||||
|
sameAccount = instanceAccount?.id === params.account.id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const instanceDomain = useSelector(getInstanceUrl)
|
||||||
|
let sameDomain = true
|
||||||
|
let statusDomain: string
|
||||||
|
switch (params.type) {
|
||||||
|
case 'status':
|
||||||
|
statusDomain = params.status.uri
|
||||||
|
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
||||||
|
: ''
|
||||||
|
sameDomain = instanceDomain === statusDomain
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const { colors } = useTheme()
|
||||||
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
|
const DEFAULT_VALUE = 350
|
||||||
|
const screenHeight = Dimensions.get('screen').height
|
||||||
|
const panY = useSharedValue(DEFAULT_VALUE)
|
||||||
|
useEffect(() => {
|
||||||
|
panY.value = withTiming(0)
|
||||||
|
}, [])
|
||||||
|
const styleTop = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
bottom: interpolate(
|
||||||
|
panY.value,
|
||||||
|
[0, screenHeight],
|
||||||
|
[0, -screenHeight],
|
||||||
|
Extrapolate.CLAMP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const dismiss = useCallback(() => {
|
||||||
|
navigation.goBack()
|
||||||
|
}, [])
|
||||||
|
const onGestureEvent = useAnimatedGestureHandler({
|
||||||
|
onActive: ({ translationY }) => {
|
||||||
|
panY.value = translationY
|
||||||
|
},
|
||||||
|
onEnd: ({ velocityY }) => {
|
||||||
|
if (velocityY > 500) {
|
||||||
|
runOnJS(dismiss)()
|
||||||
|
} else {
|
||||||
|
panY.value = withTiming(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const actions = () => {
|
||||||
switch (params.type) {
|
switch (params.type) {
|
||||||
case 'status':
|
case 'status':
|
||||||
sameAccount = instanceAccount?.id === params.status.account.id
|
return (
|
||||||
break
|
<>
|
||||||
case 'account':
|
{!sameAccount ? (
|
||||||
sameAccount = instanceAccount?.id === params.account.id
|
<ActionsAccount
|
||||||
break
|
queryKey={params.queryKey}
|
||||||
}
|
rootQueryKey={params.rootQueryKey}
|
||||||
|
account={params.status.account}
|
||||||
const instanceDomain = useSelector(getInstanceUrl)
|
dismiss={dismiss}
|
||||||
let sameDomain = true
|
|
||||||
let statusDomain: string
|
|
||||||
switch (params.type) {
|
|
||||||
case 'status':
|
|
||||||
statusDomain = params.status.uri
|
|
||||||
? params.status.uri.split(new RegExp(/\/\/(.*?)\//))[1]
|
|
||||||
: ''
|
|
||||||
sameDomain = instanceDomain === statusDomain
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
const { colors } = useTheme()
|
|
||||||
const insets = useSafeAreaInsets()
|
|
||||||
|
|
||||||
const DEFAULT_VALUE = 350
|
|
||||||
const screenHeight = Dimensions.get('screen').height
|
|
||||||
const panY = useSharedValue(DEFAULT_VALUE)
|
|
||||||
useEffect(() => {
|
|
||||||
panY.value = withTiming(0)
|
|
||||||
}, [])
|
|
||||||
const styleTop = useAnimatedStyle(() => {
|
|
||||||
return {
|
|
||||||
bottom: interpolate(
|
|
||||||
panY.value,
|
|
||||||
[0, screenHeight],
|
|
||||||
[0, -screenHeight],
|
|
||||||
Extrapolate.CLAMP
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const dismiss = useCallback(() => {
|
|
||||||
navigation.goBack()
|
|
||||||
}, [])
|
|
||||||
const onGestureEvent = useAnimatedGestureHandler({
|
|
||||||
onActive: ({ translationY }) => {
|
|
||||||
panY.value = translationY
|
|
||||||
},
|
|
||||||
onEnd: ({ velocityY }) => {
|
|
||||||
if (velocityY > 500) {
|
|
||||||
runOnJS(dismiss)()
|
|
||||||
} else {
|
|
||||||
panY.value = withTiming(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const actions = useMemo(() => {
|
|
||||||
switch (params.type) {
|
|
||||||
case 'status':
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{!sameAccount ? (
|
|
||||||
<ActionsAccount
|
|
||||||
queryKey={params.queryKey}
|
|
||||||
rootQueryKey={params.rootQueryKey}
|
|
||||||
account={params.status.account}
|
|
||||||
dismiss={dismiss}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{sameAccount && params.status ? (
|
|
||||||
<ActionsStatus
|
|
||||||
navigation={navigation}
|
|
||||||
queryKey={params.queryKey}
|
|
||||||
rootQueryKey={params.rootQueryKey}
|
|
||||||
status={params.status}
|
|
||||||
dismiss={dismiss}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{!sameDomain && statusDomain ? (
|
|
||||||
<ActionsDomain
|
|
||||||
queryKey={params.queryKey}
|
|
||||||
rootQueryKey={params.rootQueryKey}
|
|
||||||
domain={statusDomain}
|
|
||||||
dismiss={dismiss}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{params.status.visibility !== 'direct' ? (
|
|
||||||
<ActionsShare
|
|
||||||
url={params.status.url || params.status.uri}
|
|
||||||
type={params.type}
|
|
||||||
dismiss={dismiss}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
<Button
|
|
||||||
type='text'
|
|
||||||
content={t('common:buttons.cancel')}
|
|
||||||
onPress={() => {
|
|
||||||
analytics('bottomsheet_acknowledge')
|
|
||||||
}}
|
|
||||||
style={styles.button}
|
|
||||||
/>
|
/>
|
||||||
</>
|
) : null}
|
||||||
)
|
{sameAccount && params.status ? (
|
||||||
case 'account':
|
<ActionsStatus
|
||||||
return (
|
navigation={navigation}
|
||||||
<>
|
queryKey={params.queryKey}
|
||||||
{!sameAccount ? (
|
rootQueryKey={params.rootQueryKey}
|
||||||
<ActionsAccount account={params.account} dismiss={dismiss} />
|
status={params.status}
|
||||||
) : null}
|
dismiss={dismiss}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{!sameDomain && statusDomain ? (
|
||||||
|
<ActionsDomain
|
||||||
|
queryKey={params.queryKey}
|
||||||
|
rootQueryKey={params.rootQueryKey}
|
||||||
|
domain={statusDomain}
|
||||||
|
dismiss={dismiss}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{params.status.visibility !== 'direct' ? (
|
||||||
<ActionsShare
|
<ActionsShare
|
||||||
url={params.account.url}
|
url={params.status.url || params.status.uri}
|
||||||
type={params.type}
|
type={params.type}
|
||||||
dismiss={dismiss}
|
dismiss={dismiss}
|
||||||
/>
|
/>
|
||||||
<Button
|
) : null}
|
||||||
type='text'
|
<Button
|
||||||
content={t('common:buttons.cancel')}
|
type='text'
|
||||||
onPress={() => {
|
content={t('common:buttons.cancel')}
|
||||||
analytics('bottomsheet_acknowledge')
|
onPress={() => {
|
||||||
}}
|
analytics('bottomsheet_acknowledge')
|
||||||
style={styles.button}
|
}}
|
||||||
/>
|
style={styles.button}
|
||||||
</>
|
/>
|
||||||
)
|
</>
|
||||||
case 'notifications_filter':
|
)
|
||||||
return <ActionsNotificationsFilter />
|
case 'account':
|
||||||
}
|
return (
|
||||||
}, [])
|
<>
|
||||||
|
{!sameAccount ? (
|
||||||
|
<ActionsAccount account={params.account} dismiss={dismiss} />
|
||||||
|
) : null}
|
||||||
|
<ActionsShare
|
||||||
|
url={params.account.url}
|
||||||
|
type={params.type}
|
||||||
|
dismiss={dismiss}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type='text'
|
||||||
|
content={t('common:buttons.cancel')}
|
||||||
|
onPress={() => {
|
||||||
|
analytics('bottomsheet_acknowledge')
|
||||||
|
}}
|
||||||
|
style={styles.button}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
case 'notifications_filter':
|
||||||
|
return <ActionsNotificationsFilter />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaProvider>
|
<SafeAreaProvider>
|
||||||
<Animated.View style={{ flex: 1 }}>
|
<Animated.View style={{ flex: 1 }}>
|
||||||
<TapGestureHandler
|
<TapGestureHandler
|
||||||
onHandlerStateChange={({ nativeEvent }) => {
|
onHandlerStateChange={({ nativeEvent }) => {
|
||||||
if (nativeEvent.state === State.ACTIVE) {
|
if (nativeEvent.state === State.ACTIVE) {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.overlay,
|
||||||
|
{ backgroundColor: colors.backgroundOverlayInvert }
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<PanGestureHandler onGestureEvent={onGestureEvent}>
|
||||||
style={[
|
<Animated.View
|
||||||
styles.overlay,
|
style={[
|
||||||
{ backgroundColor: colors.backgroundOverlayInvert }
|
styles.container,
|
||||||
]}
|
styleTop,
|
||||||
>
|
{
|
||||||
<PanGestureHandler onGestureEvent={onGestureEvent}>
|
backgroundColor: colors.backgroundDefault,
|
||||||
<Animated.View
|
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.handle,
|
||||||
styleTop,
|
{ backgroundColor: colors.primaryOverlay }
|
||||||
{
|
|
||||||
backgroundColor: colors.backgroundDefault,
|
|
||||||
paddingBottom: insets.bottom || StyleConstants.Spacing.L
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
>
|
/>
|
||||||
<View
|
{actions()}
|
||||||
style={[
|
</Animated.View>
|
||||||
styles.handle,
|
</PanGestureHandler>
|
||||||
{ backgroundColor: colors.primaryOverlay }
|
</Animated.View>
|
||||||
]}
|
</TapGestureHandler>
|
||||||
/>
|
</Animated.View>
|
||||||
{actions}
|
</SafeAreaProvider>
|
||||||
</Animated.View>
|
)
|
||||||
</PanGestureHandler>
|
}
|
||||||
</Animated.View>
|
|
||||||
</TapGestureHandler>
|
|
||||||
</Animated.View>
|
|
||||||
</SafeAreaProvider>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
() => true
|
|
||||||
)
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
overlay: {
|
overlay: {
|
||||||
|
@ -305,7 +305,6 @@ const ScreenCompose: React.FC<RootStackScreenProps<'Screen-Compose'>> = ({
|
|||||||
|
|
||||||
switch (params?.type) {
|
switch (params?.type) {
|
||||||
case 'edit':
|
case 'edit':
|
||||||
console.log('firing mutation')
|
|
||||||
mutateTimeline.mutate({
|
mutateTimeline.mutate({
|
||||||
type: 'editItem',
|
type: 'editItem',
|
||||||
queryKey: params.queryKey,
|
queryKey: params.queryKey,
|
||||||
|
@ -7,15 +7,12 @@ const editItem = ({
|
|||||||
rootQueryKey,
|
rootQueryKey,
|
||||||
status
|
status
|
||||||
}: MutationVarsTimelineEditItem) => {
|
}: MutationVarsTimelineEditItem) => {
|
||||||
console.log('START')
|
|
||||||
queryKey &&
|
queryKey &&
|
||||||
queryClient.setQueryData<InfiniteData<any> | undefined>(queryKey, old => {
|
queryClient.setQueryData<InfiniteData<any> | undefined>(queryKey, old => {
|
||||||
if (old) {
|
if (old) {
|
||||||
old.pages = old.pages.map(page => {
|
old.pages = old.pages.map(page => {
|
||||||
page.body = page.body.map((item: Mastodon.Status) => {
|
page.body = page.body.map((item: Mastodon.Status) => {
|
||||||
if (item.id === status.id) {
|
if (item.id === status.id) {
|
||||||
console.log('found queryKey', queryKey)
|
|
||||||
console.log('new content', status.content)
|
|
||||||
item = status
|
item = status
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
@ -34,8 +31,6 @@ const editItem = ({
|
|||||||
old.pages = old.pages.map(page => {
|
old.pages = old.pages.map(page => {
|
||||||
page.body = page.body.map((item: Mastodon.Status) => {
|
page.body = page.body.map((item: Mastodon.Status) => {
|
||||||
if (item.id === status.id) {
|
if (item.id === status.id) {
|
||||||
console.log('found rootQueryKey', queryKey)
|
|
||||||
console.log('new content', status.content)
|
|
||||||
item = status
|
item = status
|
||||||
}
|
}
|
||||||
return item
|
return item
|
||||||
@ -46,7 +41,6 @@ const editItem = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
console.log('EDN')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default editItem
|
export default editItem
|
||||||
|
Loading…
x
Reference in New Issue
Block a user