diff --git a/src/components/Account.tsx b/src/components/Account.tsx index b0b9fdba..75ddfac4 100644 --- a/src/components/Account.tsx +++ b/src/components/Account.tsx @@ -42,7 +42,7 @@ const ComponentAccount: React.FC = ({ account, props, style={{ width: StyleConstants.Avatar.S, height: StyleConstants.Avatar.S, - borderRadius: 8, + borderRadius: StyleConstants.BorderRadius, marginRight: StyleConstants.Spacing.S }} dim diff --git a/src/components/AccountButton.tsx b/src/components/AccountButton.tsx index 9e481745..f6ea0a49 100644 --- a/src/components/AccountButton.tsx +++ b/src/components/AccountButton.tsx @@ -45,7 +45,7 @@ const AccountButton: React.FC = ({ account, additionalActions }) => { width: StyleConstants.Font.Size.L, height: StyleConstants.Font.Size.L }} - style={{ borderRadius: StyleConstants.Font.Size.L / 2, overflow: 'hidden' }} + style={{ borderRadius: 99, overflow: 'hidden' }} /> = ({ }} style={[ { - borderRadius: 100, + borderRadius: 99, justifyContent: 'center', alignItems: 'center', borderWidth: overlay ? 0 : selected ? 1.5 : 1, diff --git a/src/components/Emojis/Button.tsx b/src/components/Emojis/Button.tsx index 98ca98d6..f9776ee5 100644 --- a/src/components/Emojis/Button.tsx +++ b/src/components/Emojis/Button.tsx @@ -35,7 +35,7 @@ const EmojisButton: React.FC = () => { borderWidth: 2, borderColor: colors.primaryDefault, padding: StyleConstants.Spacing.Global.PagePadding / 2, - borderRadius: 100 + borderRadius: 99 }} > = ({ minWidth: 44, marginLeft: native ? -StyleConstants.Spacing.S : StyleConstants.Spacing.S, ...(type === undefined && { - borderRadius: 100 + borderRadius: 99 }), ...(type === 'text' && { paddingHorizontal: StyleConstants.Spacing.S diff --git a/src/components/Header/Right.tsx b/src/components/Header/Right.tsx index 91ab09f2..a918cbf9 100644 --- a/src/components/Header/Right.tsx +++ b/src/components/Header/Right.tsx @@ -98,9 +98,7 @@ const HeaderRight: React.FC = ({ minHeight: 44, minWidth: 44, marginRight: native ? -StyleConstants.Spacing.S : StyleConstants.Spacing.S, - ...(type === undefined && { - borderRadius: 100 - }), + ...(type === undefined && { borderRadius: 99 }), ...(type === 'text' && { paddingHorizontal: StyleConstants.Spacing.S }) diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx index 28967bc5..c381ccb7 100644 --- a/src/components/Menu/Row.tsx +++ b/src/components/Menu/Row.tsx @@ -89,7 +89,7 @@ const MenuRow: React.FC = ({ width: 8, height: 8, backgroundColor: colors.red, - borderRadius: 8, + borderRadius: StyleConstants.BorderRadius, marginRight: StyleConstants.Spacing.S }} /> diff --git a/src/components/SwipeToActions.tsx b/src/components/SwipeToActions.tsx index 74f6e01f..4306f8d2 100644 --- a/src/components/SwipeToActions.tsx +++ b/src/components/SwipeToActions.tsx @@ -31,9 +31,11 @@ export const SwipeToActions = ({ haptics(action.haptic || 'Light') action.onPress({ item }) }} + style={{ backgroundColor: 'rgba(0, 255, 0, 0.2)' }} > = ({ conversation, queryKey, highlig = ({ total, index, sensitiveShown, audio width: '100%', height: StyleConstants.Spacing.M + StyleConstants.Spacing.S * 2, paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, - borderRadius: 100, + borderRadius: 99, opacity: sensitiveShown ? 0.35 : undefined }} > diff --git a/src/components/Timeline/Shared/Avatar.tsx b/src/components/Timeline/Shared/Avatar.tsx index ff80d0ab..55aa6ed0 100644 --- a/src/components/Timeline/Shared/Avatar.tsx +++ b/src/components/Timeline/Shared/Avatar.tsx @@ -49,7 +49,7 @@ const TimelineAvatar: React.FC = ({ account }) => { } } style={{ - borderRadius: StyleConstants.Avatar.M, + borderRadius: 99, overflow: 'hidden', marginRight: StyleConstants.Spacing.S }} diff --git a/src/components/Timeline/Shared/Card.tsx b/src/components/Timeline/Shared/Card.tsx index 2f7adac7..86e2ae4e 100644 --- a/src/components/Timeline/Shared/Card.tsx +++ b/src/components/Timeline/Shared/Card.tsx @@ -137,7 +137,7 @@ const TimelineCard: React.FC = () => { flexDirection: 'row', marginTop: StyleConstants.Spacing.M, borderWidth: 1, - borderRadius: StyleConstants.Spacing.S, + borderRadius: StyleConstants.BorderRadius, overflow: 'hidden', borderColor: colors.border }} diff --git a/src/components/discardConfirmation.ts b/src/components/discardConfirmation.ts new file mode 100644 index 00000000..f3f4fbb4 --- /dev/null +++ b/src/components/discardConfirmation.ts @@ -0,0 +1,26 @@ +import i18n from '@i18n/index' +import { Alert } from 'react-native' + +export const discardConfirmation = ({ + condition, + action +}: { + condition: boolean + action: () => void +}) => { + if (condition) { + Alert.alert(i18n.t('common:discard.title'), i18n.t('common:discard.message'), [ + { + text: i18n.t('common:buttons.discard'), + style: 'destructive', + onPress: () => action() + }, + { + text: i18n.t('common:buttons.cancel'), + style: 'default' + } + ]) + } else { + action() + } +} diff --git a/src/components/mediaSelector.ts b/src/components/mediaSelector.ts index 4ef0ccfd..e05aedde 100644 --- a/src/components/mediaSelector.ts +++ b/src/components/mediaSelector.ts @@ -5,7 +5,7 @@ import i18next from 'i18next' import { Asset, launchImageLibrary } from 'react-native-image-picker' const queryKeyInstance: QueryKeyInstance = ['Instance'] -export const MAX_MEDIA_ATTACHMENTS: number = +export const MAX_MEDIA_ATTACHMENTS = (): number => queryClient.getQueryData>(queryKeyInstance)?.configuration?.statuses .max_media_attachments || 4 @@ -27,7 +27,7 @@ const mediaSelector = async ({ indicateMaximum = false, showActionSheetWithOptions }: Props): Promise => { - const _maximum = maximum || MAX_MEDIA_ATTACHMENTS + const _maximum = maximum || MAX_MEDIA_ATTACHMENTS() const options = () => { switch (mediaType) { diff --git a/src/screens/AccountSelection.tsx b/src/screens/AccountSelection.tsx index 7681ca8f..1e3ce4e1 100644 --- a/src/screens/AccountSelection.tsx +++ b/src/screens/AccountSelection.tsx @@ -52,7 +52,7 @@ const Share = ({ padding: StyleConstants.Spacing.M, borderWidth: 1, borderColor: colors.shimmerHighlight, - borderRadius: 8 + borderRadius: StyleConstants.BorderRadius }} children={text} /> @@ -65,7 +65,7 @@ const Share = ({ padding: StyleConstants.Spacing.M, borderWidth: 1, borderColor: colors.shimmerHighlight, - borderRadius: 8 + borderRadius: StyleConstants.BorderRadius }} > padding: StyleConstants.Spacing.Global.PagePadding, marginTop: StyleConstants.Spacing.Global.PagePadding, borderWidth: 1, - borderRadius: 6, + borderRadius: StyleConstants.BorderRadius, borderColor: colors.primaryDefault, backgroundColor: colors.backgroundDefault }} @@ -123,7 +123,7 @@ const ScreenAnnouncements: React.FC marginTop: StyleConstants.Spacing.Global.PagePadding / 2, marginBottom: StyleConstants.Spacing.Global.PagePadding / 2, marginRight: StyleConstants.Spacing.M, - borderRadius: 6, + borderRadius: StyleConstants.BorderRadius, flexDirection: 'row', borderColor: reaction.me ? colors.disabled : colors.primaryDefault, backgroundColor: reaction.me ? colors.disabled : colors.backgroundDefault @@ -231,7 +231,7 @@ const ScreenAnnouncements: React.FC style={{ width: StyleConstants.Spacing.S, height: StyleConstants.Spacing.S, - borderRadius: StyleConstants.Spacing.S, + borderRadius: StyleConstants.BorderRadius, borderWidth: 1, borderColor: colors.primaryDefault, backgroundColor: i === index ? colors.primaryDefault : undefined, @@ -271,7 +271,7 @@ const ScreenAnnouncements: React.FC style={{ width: StyleConstants.Spacing.S, height: StyleConstants.Spacing.S, - borderRadius: StyleConstants.Spacing.S, + borderRadius: StyleConstants.BorderRadius, borderWidth: 1, borderColor: colors.primaryDefault, backgroundColor: i === index ? colors.primaryDefault : undefined, diff --git a/src/screens/Compose/DraftsList.tsx b/src/screens/Compose/DraftsList.tsx index fba9e684..0d4ea949 100644 --- a/src/screens/Compose/DraftsList.tsx +++ b/src/screens/Compose/DraftsList.tsx @@ -56,7 +56,7 @@ const ComposeDraftsList: React.FC diff --git a/src/screens/Compose/EditAttachment.tsx b/src/screens/Compose/EditAttachment.tsx index c7a9b096..f8442b76 100644 --- a/src/screens/Compose/EditAttachment.tsx +++ b/src/screens/Compose/EditAttachment.tsx @@ -1,3 +1,4 @@ +import { discardConfirmation } from '@components/discardConfirmation' import haptics from '@components/haptics' import { HeaderLeft, HeaderRight } from '@components/Header' import { ModalScrollView } from '@components/ModalScrollView' @@ -6,9 +7,11 @@ import apiInstance from '@utils/api/instance' import { ScreenComposeStackScreenProps } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' +import { Image } from 'expo-image' import React, { useContext, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Alert, TextInput } from 'react-native' +import { Alert, TextInput, View } from 'react-native' +import { DEFAULT_WIDTH } from './Root/Footer/Attachments' import ComposeContext from './utils/createContext' const ComposeEditAttachment: React.FC< @@ -20,7 +23,7 @@ const ComposeEditAttachment: React.FC< } }) => { const { colors } = useTheme() - const { t } = useTranslation('screenCompose') + const { t } = useTranslation(['common', 'screenCompose']) const { composeState, composeDispatch } = useContext(ComposeContext) const [isSubmitting, setIsSubmitting] = useState(false) @@ -31,55 +34,88 @@ const ComposeEditAttachment: React.FC< return null } + const [altText, setAltText] = useState(theAttachment.description) + useEffect(() => { navigation.setOptions({ - title: t('content.editAttachment.header.title'), - headerLeft: () => navigation.goBack()} />, + title: t('screenCompose:content.editAttachment.header.title'), + headerLeft: () => ( + { + discardConfirmation({ + condition: theAttachment.description != altText, + action: () => navigation.goBack() + }) + }} + /> + ), headerRight: () => ( { if (composeState.type === 'edit') { - composeDispatch({ type: 'attachment/edit', payload: { ...theAttachment } }) + composeDispatch({ + type: 'attachment/edit', + payload: { ...theAttachment, description: altText } + }) navigation.goBack() return } - setIsSubmitting(true) - const body = { description: theAttachment.description } - theAttachment?.id && apiInstance({ method: 'put', url: `media/${theAttachment.id}`, - body + body: { description: altText } }) - .then(() => { + .then(res => { + setIsSubmitting(false) haptics('Success') + composeDispatch({ + type: 'attachment/edit', + payload: res.body + }) navigation.goBack() }) .catch(() => { setIsSubmitting(false) haptics('Error') - Alert.alert(t('content.editAttachment.header.right.failed.title'), undefined, [ - { - text: t('content.editAttachment.header.right.failed.button'), - style: 'cancel' - } - ]) + Alert.alert( + t('screenCompose:content.editAttachment.header.right.failed.title'), + undefined, + [ + { + text: t('screenCompose:content.editAttachment.header.right.failed.button'), + style: 'cancel' + } + ] + ) }) }} /> ) }) - }, [theAttachment]) + }, [theAttachment, altText]) return ( + + + - {t('content.editAttachment.content.altText.heading')} + {t('screenCompose:content.editAttachment.content.altText.heading')} - composeDispatch({ - type: 'attachment/edit', - payload: { - ...theAttachment, - description: e - } - }) - } - placeholder={t('content.editAttachment.content.altText.placeholder')} + value={altText} + onChangeText={e => setAltText(e)} + placeholder={t('screenCompose:content.editAttachment.content.altText.placeholder')} placeholderTextColor={colors.secondary} - value={theAttachment.description} /> { const attachmentOnPress = () => { if (composeState.poll.active) return - if (composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS) { + if (composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS()) { return chooseAndUploadAttachment({ composeDispatch, showActionSheetWithOptions }) } } diff --git a/src/screens/Compose/Root/Footer/Attachments.tsx b/src/screens/Compose/Root/Footer/Attachments.tsx index d1db2c3a..f84f6411 100644 --- a/src/screens/Compose/Root/Footer/Attachments.tsx +++ b/src/screens/Compose/Root/Footer/Attachments.tsx @@ -12,19 +12,19 @@ import { StyleConstants } from '@utils/styles/constants' import layoutAnimation from '@utils/styles/layoutAnimation' import { useTheme } from '@utils/styles/ThemeManager' import { Image } from 'expo-image' -import React, { RefObject, useContext, useEffect, useRef } from 'react' +import React, { RefObject, useContext, useRef } from 'react' import { useTranslation } from 'react-i18next' import { FlatList, Pressable, StyleSheet, View } from 'react-native' import ComposeContext from '../../utils/createContext' import { ExtendedAttachment } from '../../utils/types' import chooseAndUploadAttachment from './addAttachment' +export const DEFAULT_WIDTH = 150 + export interface Props { accessibleRefAttachments: RefObject } -const DEFAULT_HEIGHT = 200 - const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => { const { showActionSheetWithOptions } = useActionSheet() const { composeState, composeDispatch } = useContext(ComposeContext) @@ -40,72 +40,22 @@ const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => { payload: { sensitive: !composeState.attachments.sensitive } }) - const calculateWidth = (item: ExtendedAttachment) => { - if (item.local) { - return ((item.local.width || 100) / (item.local.height || 100)) * DEFAULT_HEIGHT - } else { - if (item.remote) { - if (item.remote.meta.original.aspect) { - return item.remote.meta.original.aspect * DEFAULT_HEIGHT - } else if (item.remote.meta.original.width && item.remote.meta.original.height) { - return ( - (item.remote.meta.original.width / item.remote.meta.original.height) * DEFAULT_HEIGHT - ) - } else { - return DEFAULT_HEIGHT - } - } else { - return DEFAULT_HEIGHT - } - } - } - - const snapToOffsets = () => { - const attachmentsOffsets = composeState.attachments.uploads.map((_, index) => { - let currentOffset = 0 - Array.from(Array(index).keys()).map( - i => - (currentOffset = - currentOffset + - calculateWidth(composeState.attachments.uploads[i]) + - StyleConstants.Spacing.Global.PagePadding) - ) - return currentOffset - }) - return attachmentsOffsets.length < 4 - ? [ - ...attachmentsOffsets, - attachmentsOffsets.reduce((a, b) => a + b, 0) + - DEFAULT_HEIGHT + - StyleConstants.Spacing.Global.PagePadding - ] - : attachmentsOffsets - } - let prevOffsets = useRef() - useEffect(() => { - const snap = snapToOffsets() - if (snap.length > (prevOffsets.current ? prevOffsets.current.length : 0)) { - flatListRef.current?.scrollToOffset({ - offset: snap[snapToOffsets.length - 2] + snap[snapToOffsets.length - 1] - }) - } - prevOffsets.current = snap - }, [snapToOffsets, prevOffsets.current]) - const renderAttachment = ({ item, index }: { item: ExtendedAttachment; index: number }) => { return ( = ({ accessibleRefAttachments }) => { paddingRight: StyleConstants.Spacing.S, paddingTop: StyleConstants.Spacing.XS, paddingBottom: StyleConstants.Spacing.XS, - color: colors.backgroundDefault, + color: colors.primaryOverlay, backgroundColor: colors.backgroundOverlayInvert }} > @@ -157,7 +107,7 @@ const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => { })} type='icon' content='x' - spacing='M' + size='L' round overlay onPress={() => { @@ -175,11 +125,11 @@ const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => { accessibilityLabel={t('content.root.footer.attachments.edit.accessibilityLabel', { attachment: index + 1 })} - type='icon' - content='edit' - spacing='M' - round overlay + size='S' + type='text' + content={!!item.remote?.description?.length ? 'ALT ✓' : '+ ALT'} + fontBold onPress={() => navigation.navigate('Screen-Compose-EditAttachment', { index })} /> ) : null} @@ -230,23 +180,23 @@ const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => { pagingEnabled={false} snapToAlignment='center' renderItem={renderAttachment} - snapToOffsets={snapToOffsets()} + snapToOffsets={new Array(composeState.attachments.uploads.length).fill(DEFAULT_WIDTH)} keyboardShouldPersistTaps='always' showsHorizontalScrollIndicator={false} data={composeState.attachments.uploads} keyExtractor={item => item.local?.uri || item.remote?.url || Math.random().toString()} ListFooterComponent={ - composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS ? ( + composeState.attachments.uploads.length < MAX_MEDIA_ATTACHMENTS() ? ( { await chooseAndUploadAttachment({ @@ -258,9 +208,7 @@ const ComposeAttachments: React.FC = ({ accessibleRefAttachments }) => {