diff --git a/src/@types/app.d.ts b/src/@types/app.d.ts index 7ca8b57a..fab4bbf3 100644 --- a/src/@types/app.d.ts +++ b/src/@types/app.d.ts @@ -10,7 +10,7 @@ declare namespace App { | 'Toot' | 'Account_Default' | 'Account_All' - | 'Account_Media' + | 'Account_Attachments' | 'Conversations' | 'Bookmarks' | 'Favourites' diff --git a/src/@types/react-navigation.d.ts b/src/@types/react-navigation.d.ts index 4d0b2881..f4225dc9 100644 --- a/src/@types/react-navigation.d.ts +++ b/src/@types/react-navigation.d.ts @@ -12,6 +12,7 @@ declare namespace Nav { account: Pick } 'Screen-Shared-Announcements': { showAll?: boolean } + 'Screen-Shared-Attachments': { account: Mastodon.Account } 'Screen-Shared-Compose': | { type: 'reply' | 'conversation' | 'edit' diff --git a/src/components/Account.tsx b/src/components/Account.tsx index 2b7dee0b..f21a007f 100644 --- a/src/components/Account.tsx +++ b/src/components/Account.tsx @@ -1,27 +1,25 @@ import { ParseEmojis } from '@components/Parse' -import { useNavigation } from '@react-navigation/native' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' -import { Image, Pressable, StyleSheet, Text, View } from 'react-native' +import { Pressable, StyleSheet, Text, View } from 'react-native' +import GracefullyImage from './GracefullyImage' export interface Props { account: Mastodon.Account + onPress: () => void } -const ComponentAccount: React.FC = ({ account }) => { - const navigation = useNavigation() +const ComponentAccount: React.FC = ({ account, onPress }) => { const { theme } = useTheme() return ( { - navigation.push('Screen-Shared-Account', { account }) - }} + onPress={onPress} > - diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 20fae0b7..3b32350d 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -155,26 +155,24 @@ const Button: React.FC = ({ } return ( - - - + ) } diff --git a/src/components/GracefullyImage.tsx b/src/components/GracefullyImage.tsx new file mode 100644 index 00000000..25ac4c37 --- /dev/null +++ b/src/components/GracefullyImage.tsx @@ -0,0 +1,171 @@ +import { StyleConstants } from '@utils/styles/constants' +import { Surface } from 'gl-react-expo' +import { Blurhash } from 'gl-react-blurhash' +import React, { useCallback, useEffect, useState } from 'react' +import { + Image, + Pressable, + StyleProp, + StyleSheet, + View, + ViewStyle +} from 'react-native' +import { Image as ImageCache } from 'react-native-expo-image-cache' +import { useTheme } from '@utils/styles/ThemeManager' + +type CancelPromise = ((reason?: Error) => void) | undefined +type ImageSize = { width: number; height: number } +interface ImageSizeOperation { + start: () => Promise + cancel: CancelPromise +} +const getImageSize = (uri: string): ImageSizeOperation => { + let cancel: CancelPromise + const start = (): Promise => + new Promise<{ width: number; height: number }>((resolve, reject) => { + cancel = reject + Image.getSize( + uri, + (width, height) => { + cancel = undefined + resolve({ width, height }) + }, + error => { + reject(error) + } + ) + }) + + return { start, cancel } +} + +export interface Props { + hidden?: boolean + cache?: boolean + uri: { preview?: string; original?: string; remote?: string } + blurhash?: string + dimension?: { width: number; height: number } + onPress?: () => void + style?: StyleProp +} + +const GracefullyImage: React.FC = ({ + hidden = false, + cache = false, + uri, + blurhash, + dimension, + onPress, + style +}) => { + const { mode, theme } = useTheme() + + const [imageVisible, setImageVisible] = useState() + const [imageLoadingFailed, setImageLoadingFailed] = useState(false) + useEffect(() => { + let cancel: CancelPromise + const sideEffect = async (): Promise => { + try { + if (uri.preview) { + const tryPreview = getImageSize(uri.preview) + cancel = tryPreview.cancel + const res = await tryPreview.start() + if (res) { + setImageVisible(uri.preview) + return + } + } + } catch (error) { + if (__DEV__) console.warn('Image preview', error) + } + + try { + const tryOriginal = getImageSize(uri.original!) + cancel = tryOriginal.cancel + const res = await tryOriginal.start() + if (res) { + setImageVisible(uri.original!) + return + } + } catch (error) { + if (__DEV__) console.warn('Image original', error) + } + + try { + if (uri.remote) { + const tryRemote = getImageSize(uri.remote) + cancel = tryRemote.cancel + const res = await tryRemote.start() + if (res) { + setImageVisible(uri.remote) + return + } + } + } catch (error) { + if (__DEV__) console.warn('Image remote', error) + } + + setImageLoadingFailed(true) + } + + if (uri.original) { + sideEffect() + } + + return () => { + if (cancel) { + cancel() + } + } + }, [uri]) + + const children = useCallback(() => { + if (imageVisible && !hidden) { + if (cache) { + return + } else { + return + } + } else if (blurhash) { + return ( + + + + ) + } else { + return ( + + ) + } + }, [hidden, mode, imageVisible]) + + return ( + + ) +} + +const styles = StyleSheet.create({ + image: { + flex: 1 + } +}) + +export default GracefullyImage diff --git a/src/components/Header/Right.tsx b/src/components/Header/Right.tsx index aa407bd9..d22508c1 100644 --- a/src/components/Header/Right.tsx +++ b/src/components/Header/Right.tsx @@ -26,13 +26,13 @@ const HeaderRight: React.FC = ({ const { theme } = useTheme() const mounted = useRef(false) - useEffect(() => { - if (mounted.current) { - layoutAnimation() - } else { - mounted.current = true - } - }, [content, loading, disabled]) + // useEffect(() => { + // if (mounted.current) { + // layoutAnimation() + // } else { + // mounted.current = true + // } + // }, [content, loading, disabled]) const loadingSpinkit = useMemo( () => ( diff --git a/src/components/Timelines.tsx b/src/components/Timelines.tsx index 5f80e329..c3605f1b 100644 --- a/src/components/Timelines.tsx +++ b/src/components/Timelines.tsx @@ -108,7 +108,7 @@ const Timelines: React.FC = ({ name, content }) => { renderTabBar={() => null} onIndexChange={index => setSegment(index)} navigationState={{ index: segment, routes }} - initialLayout={{ width: Dimensions.get('window').width }} + initialLayout={{ width: Dimensions.get('screen').width }} /> )} diff --git a/src/components/Timelines/Hashtag.tsx b/src/components/Timelines/Hashtag.tsx new file mode 100644 index 00000000..555b1cb9 --- /dev/null +++ b/src/components/Timelines/Hashtag.tsx @@ -0,0 +1,36 @@ +import { StyleConstants } from '@utils/styles/constants' +import { useTheme } from '@utils/styles/ThemeManager' +import React from 'react' +import { Pressable, StyleSheet, Text } from 'react-native' + +export interface Props { + tag: Mastodon.Tag + onPress: () => void +} + +const ComponentHashtag: React.FC = ({ tag, onPress }) => { + const { theme } = useTheme() + + return ( + + + #{tag.name} + + + ) +} + +const styles = StyleSheet.create({ + itemDefault: { + padding: StyleConstants.Spacing.S * 1.5, + borderBottomWidth: StyleSheet.hairlineWidth + }, + itemHashtag: { + ...StyleConstants.FontStyle.M + } +}) + +export default ComponentHashtag diff --git a/src/components/Timelines/Timeline.tsx b/src/components/Timelines/Timeline.tsx index 703aff2f..77fa711f 100644 --- a/src/components/Timelines/Timeline.tsx +++ b/src/components/Timelines/Timeline.tsx @@ -232,10 +232,10 @@ const Timeline: React.FC = ({ {...(queryKey && queryKey[1].page === 'RemotePublic' && { ListHeaderComponent })} {...(toot && isSuccess && { onScrollToIndexFailed })} - // maintainVisibleContentPosition={{ - // minIndexForVisible: 0, - // autoscrollToTopThreshold: 2 - // }} + maintainVisibleContentPosition={{ + minIndexForVisible: 0, + autoscrollToTopThreshold: 1 + }} {...customProps} /> ) diff --git a/src/components/Timelines/Timeline/Shared/Attachment.tsx b/src/components/Timelines/Timeline/Shared/Attachment.tsx index b2589bc3..e78b647a 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment.tsx @@ -101,8 +101,8 @@ const TimelineAttachment: React.FC = ({ status }) => { ) return ( - - {attachments} + + {attachments} {status.sensitive && (sensitiveShown ? ( @@ -123,7 +123,7 @@ const TimelineAttachment: React.FC = ({ status }) => { onPress={onPressShow} style={{ position: 'absolute', - top: StyleConstants.Spacing.S, + top: StyleConstants.Spacing.S * 2, left: StyleConstants.Spacing.S }} /> @@ -133,7 +133,7 @@ const TimelineAttachment: React.FC = ({ status }) => { } const styles = StyleSheet.create({ - base: { + container: { marginTop: StyleConstants.Spacing.S, flex: 1, flexDirection: 'row', @@ -141,10 +141,6 @@ const styles = StyleSheet.create({ justifyContent: 'center', alignContent: 'stretch' }, - container: { - flexBasis: '50%', - aspectRatio: 16 / 9 - }, sensitiveBlur: { position: 'absolute', width: '100%', diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx index 9d593d4e..9f7b2a43 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx @@ -6,7 +6,8 @@ import { Audio } from 'expo-av' import { Surface } from 'gl-react-expo' import { Blurhash } from 'gl-react-blurhash' import React, { useCallback, useState } from 'react' -import { Image, StyleSheet, View } from 'react-native' +import { StyleSheet, View } from 'react-native' +import GracefullyImage from '@components/GracefullyImage' export interface Props { sensitiveShown: boolean @@ -38,7 +39,7 @@ const AttachmentAudio: React.FC = ({ sensitiveShown, audio }) => { audioPlayer!.pauseAsync() setAudioPlaying(false) }, [audioPlayer]) - + console.log(audio) return ( @@ -56,9 +57,11 @@ const AttachmentAudio: React.FC = ({ sensitiveShown, audio }) => { ) : ( <> {(audio.preview_url || audio.preview_remote_url) && ( - )}