diff --git a/assets/icon.png b/assets/icon.png index 093d16a7..ab0e8cbc 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/assets/splash.png b/assets/splash.png index fdffaacc..bd2004e8 100644 Binary files a/assets/splash.png and b/assets/splash.png differ diff --git a/package.json b/package.json index e1b52115..4c3ae5b6 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "expo-secure-store": "~9.3.0", "expo-splash-screen": "~0.8.1", "expo-status-bar": "~1.0.3", + "expo-store-review": "~2.3.0", "expo-video-thumbnails": "~4.4.0", "expo-web-browser": "~8.6.0", "gl-react": "^4.0.1", diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 3b32350d..c0ed634b 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -122,7 +122,7 @@ const Button: React.FC = ({ style={{ opacity: loading ? 0 : 1 }} size={StyleConstants.Font.Size[size] * (size === 'L' ? 1.25 : 1)} /> - {loading && loadingSpinkit} + {loading ? loadingSpinkit : null} ) case 'text': @@ -141,7 +141,7 @@ const Button: React.FC = ({ children={content} testID='text' /> - {loading && loadingSpinkit} + {loading ? loadingSpinkit : null} ) } diff --git a/src/components/GracefullyImage.tsx b/src/components/GracefullyImage.tsx index 09c5d10d..bf4ab449 100644 --- a/src/components/GracefullyImage.tsx +++ b/src/components/GracefullyImage.tsx @@ -14,24 +14,55 @@ 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 + start: () => Promise cancel: CancelPromise } -const getImageSize = (uri: string): ImageSizeOperation => { +const getImageSize = ({ + preview, + original, + remote +}: { + preview?: string + original: string + remote?: string +}): ImageSizeOperation => { let cancel: CancelPromise - const start = (): Promise => - new Promise<{ width: number; height: number }>((resolve, reject) => { + const start = (): Promise => + new Promise((resolve, reject) => { cancel = reject Image.getSize( - uri, - (width, height) => { + preview || '', + () => { cancel = undefined - resolve({ width, height }) + resolve(preview!) }, - error => { - reject(error) + () => { + cancel = reject + Image.getSize( + original, + () => { + cancel = undefined + resolve(original) + }, + () => { + cancel = reject + if (!remote) { + reject() + } else { + Image.getSize( + remote, + () => { + cancel = undefined + resolve(remote) + }, + error => { + reject(error) + } + ) + } + } + ) } ) }) @@ -61,51 +92,22 @@ const GracefullyImage: React.FC = ({ const { mode, theme } = useTheme() const [imageVisible, setImageVisible] = useState() - const [imageLoadingFailed, setImageLoadingFailed] = useState(false) + useEffect(() => { + let mounted = true 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 - } + const prefetchImage = getImageSize(uri as { original: string }) + cancel = prefetchImage.cancel + const res = await prefetchImage.start() + if (mounted) { + setImageVisible(res) } + 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) { @@ -113,6 +115,7 @@ const GracefullyImage: React.FC = ({ } return () => { + mounted = false if (cancel) { cancel() } diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx index 6327addc..cdcc4c8e 100644 --- a/src/components/Menu/Row.tsx +++ b/src/components/Menu/Row.tsx @@ -13,7 +13,7 @@ export interface Props { title: string description?: string - content?: string + content?: string | React.ReactNode switchValue?: boolean switchDisabled?: boolean @@ -90,26 +90,28 @@ const MenuRow: React.FC = ({ - {(content && content.length) || - switchValue !== undefined || - iconBack ? ( + {content || switchValue !== undefined || iconBack ? ( - {content && content.length ? ( - <> - - {content} - - {loading && !iconBack && loadingSpinkit} - + {content ? ( + typeof content === 'string' ? ( + <> + + {content} + + {loading && !iconBack && loadingSpinkit} + + ) : ( + content + ) ) : null} {switchValue !== undefined ? ( = ({ ) }, []) + const publicRemoteNotice = useSelector(getPublicRemoteNotice).hidden + useScrollToTop(flRef) return ( @@ -231,7 +234,8 @@ const Timeline: React.FC = ({ {...(!disableRefresh && { refreshControl })} ItemSeparatorComponent={ItemSeparatorComponent} {...(queryKey && - queryKey[1].page === 'RemotePublic' && { ListHeaderComponent })} + queryKey[1].page === 'RemotePublic' && + !publicRemoteNotice && { ListHeaderComponent })} {...(toot && isSuccess && { onScrollToIndexFailed })} maintainVisibleContentPosition={{ minIndexForVisible: 0, diff --git a/src/components/Timelines/Timeline/Header.tsx b/src/components/Timelines/Timeline/Header.tsx index 7489c29c..f9d2453d 100644 --- a/src/components/Timelines/Timeline/Header.tsx +++ b/src/components/Timelines/Timeline/Header.tsx @@ -2,11 +2,14 @@ import { useNavigation } from '@react-navigation/native' import Icon from '@root/components/Icon' import { StyleConstants } from '@root/utils/styles/constants' import { useTheme } from '@root/utils/styles/ThemeManager' +import { updatePublicRemoteNotice } from '@utils/slices/contextsSlice' import React from 'react' import { StyleSheet, Text, View } from 'react-native' +import { useDispatch } from 'react-redux' const TimelineHeader = React.memo( () => { + const dispatch = useDispatch() const navigation = useNavigation() const { theme } = useTheme() @@ -17,6 +20,7 @@ const TimelineHeader = React.memo( { + dispatch(updatePublicRemoteNotice(1)) navigation.navigate('Screen-Me', { screen: 'Screen-Me-Root', params: { navigateAway: 'Screen-Me-Settings-UpdateRemote' } diff --git a/src/components/Timelines/Timeline/Shared/Attachment.tsx b/src/components/Timelines/Timeline/Shared/Attachment.tsx index e78b647a..ed5ba625 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment.tsx @@ -57,9 +57,10 @@ const TimelineAttachment: React.FC = ({ status }) => { return ( ) @@ -67,6 +68,8 @@ const TimelineAttachment: React.FC = ({ status }) => { return ( @@ -75,6 +78,8 @@ const TimelineAttachment: React.FC = ({ status }) => { return ( @@ -83,6 +88,8 @@ const TimelineAttachment: React.FC = ({ status }) => { return ( @@ -91,6 +98,8 @@ const TimelineAttachment: React.FC = ({ status }) => { return ( diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx index 9f7b2a43..cbaa0bde 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment/Audio.tsx @@ -1,4 +1,5 @@ import Button from '@components/Button' +import GracefullyImage from '@components/GracefullyImage' import { Slider } from '@sharcoux/slider' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' @@ -7,14 +8,21 @@ import { Surface } from 'gl-react-expo' import { Blurhash } from 'gl-react-blurhash' import React, { useCallback, useState } from 'react' import { StyleSheet, View } from 'react-native' -import GracefullyImage from '@components/GracefullyImage' +import attachmentAspectRatio from './aspectRatio' export interface Props { + total: number + index: number sensitiveShown: boolean audio: Mastodon.AttachmentAudio } -const AttachmentAudio: React.FC = ({ sensitiveShown, audio }) => { +const AttachmentAudio: React.FC = ({ + total, + index, + sensitiveShown, + audio +}) => { const { theme } = useTheme() const [audioPlayer, setAudioPlayer] = useState() @@ -39,9 +47,17 @@ const AttachmentAudio: React.FC = ({ sensitiveShown, audio }) => { audioPlayer!.pauseAsync() setAudioPlaying(false) }, [audioPlayer]) - console.log(audio) + return ( - + {sensitiveShown ? ( audio.blurhash && ( @@ -116,7 +132,6 @@ const styles = StyleSheet.create({ base: { flex: 1, flexBasis: '50%', - aspectRatio: 16 / 9, padding: StyleConstants.Spacing.XS / 2, flexDirection: 'row' }, diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Image.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Image.tsx index 957d7f97..80d758ef 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment/Image.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment/Image.tsx @@ -1,22 +1,25 @@ +import GracefullyImage from '@components/GracefullyImage' +import { StyleConstants } from '@utils/styles/constants' import React, { useCallback } from 'react' import { StyleSheet } from 'react-native' -import { StyleConstants } from '@utils/styles/constants' -import GracefullyImage from '@components/GracefullyImage' +import attachmentAspectRatio from './aspectRatio' export interface Props { + total: number + index: number sensitiveShown: boolean image: Mastodon.AttachmentImage - imageIndex: number navigateToImagesViewer: (imageIndex: number) => void } const AttachmentImage: React.FC = ({ + total, + index, sensitiveShown, image, - imageIndex, navigateToImagesViewer }) => { - const onPress = useCallback(() => navigateToImagesViewer(imageIndex), []) + const onPress = useCallback(() => navigateToImagesViewer(index), []) return ( = ({ }} blurhash={image.blurhash} onPress={onPress} - style={styles.base} + style={[ + styles.base, + { aspectRatio: attachmentAspectRatio({ total, index }) } + ]} /> ) } @@ -37,7 +43,6 @@ const styles = StyleSheet.create({ base: { flex: 1, flexBasis: '50%', - aspectRatio: 16 / 9, padding: StyleConstants.Spacing.XS / 2 } }) diff --git a/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx b/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx index da310940..11f5c0ea 100644 --- a/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx +++ b/src/components/Timelines/Timeline/Shared/Attachment/Unsupported.tsx @@ -7,13 +7,18 @@ import { Surface } from 'gl-react-expo' import React from 'react' import { useTranslation } from 'react-i18next' import { StyleSheet, Text, View } from 'react-native' +import attachmentAspectRatio from './aspectRatio' export interface Props { + total: number + index: number sensitiveShown: boolean attachment: Mastodon.AttachmentUnknown } const AttachmentUnsupported: React.FC = ({ + total, + index, sensitiveShown, attachment }) => { @@ -21,7 +26,12 @@ const AttachmentUnsupported: React.FC = ({ const { theme } = useTheme() return ( - + {attachment.blurhash ? ( = ({ sensitiveShown, video }) => { +const AttachmentVideo: React.FC = ({ + total, + index, + sensitiveShown, + video +}) => { const videoPlayer = useRef