From 4503fb991b3a47e772e2864f391ec2bd0db99eb0 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Thu, 5 May 2022 23:03:00 +0200 Subject: [PATCH 01/27] Android sharing working --- android/app/src/main/AndroidManifest.xml | 20 ++- ...@types+react-native-share-menu+5.0.2.patch | 12 +- src/Screens.tsx | 140 +++++++++++++----- 3 files changed, 132 insertions(+), 40 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7a814b0f..46884301 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -31,12 +31,30 @@ + + + + + + + + + + + + + + - + + + + + diff --git a/patches/@types+react-native-share-menu+5.0.2.patch b/patches/@types+react-native-share-menu+5.0.2.patch index 997211e6..9d30b147 100644 --- a/patches/@types+react-native-share-menu+5.0.2.patch +++ b/patches/@types+react-native-share-menu+5.0.2.patch @@ -1,18 +1,22 @@ diff --git a/node_modules/@types/react-native-share-menu/index.d.ts b/node_modules/@types/react-native-share-menu/index.d.ts -index f52822c..b1d3bdd 100755 +index f52822c..ee98565 100755 --- a/node_modules/@types/react-native-share-menu/index.d.ts +++ b/node_modules/@types/react-native-share-menu/index.d.ts -@@ -6,9 +6,7 @@ +@@ -5,11 +5,9 @@ + // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // Minimum TypeScript Version: 3.7 - export interface ShareData { +-export interface ShareData { - mimeType: string; - data: string | string[]; - extraData?: object | undefined; +-} ++export type ShareData = { + data: {mimeType: string; data: string}[]; - } ++} | {mimeType: string; data: string | string[]} export type ShareCallback = (share?: ShareData) => void; + @@ -28,7 +26,7 @@ interface ShareMenuReactView { dismissExtension(error?: string): void; openApp(): void; diff --git a/src/Screens.tsx b/src/Screens.tsx index 9065ee5d..8420c867 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -161,61 +161,131 @@ const Screens: React.FC = ({ localCorrupt }) => { // Share Extension const handleShare = useCallback( - (item?: { data?: { mimeType: string; data: string }[] }) => { + ( + item?: + | { + data: { mimeType: string; data: string }[] + mimeType: undefined + } + | { data: string | string[]; mimeType: string } + ) => { if (instanceActive < 0) { return } - if (!item || !item.data || !Array.isArray(item.data) || !item.data) { + if (!item || !item.data) { return } let text: string | undefined = undefined let images: { type: string; uri: string }[] = [] let video: { type: string; uri: string } | undefined = undefined - item.data.forEach((d, i) => { - const typesImage = ['png', 'jpg', 'jpeg', 'gif'] - const typesVideo = ['mp4', 'm4v', 'mov', 'webm'] - const { mimeType, data } = d - console.log('mimeType', mimeType) - console.log('data', data) - if (mimeType.startsWith('image/')) { - if (!typesImage.includes(mimeType.split('/')[1])) { - console.warn('Image type not supported:', mimeType.split('/')[1]) + + switch (Platform.OS) { + case 'ios': + if (!Array.isArray(item.data) || !item.data) { return } - images.push({ type: mimeType.split('/')[1], uri: data }) - } else if (mimeType.startsWith('video/')) { - if (!typesVideo.includes(mimeType.split('/')[1])) { - console.warn('Video type not supported:', mimeType.split('/')[1]) + + item.data.forEach(d => { + if (typeof d === 'string') return + const typesImage = ['png', 'jpg', 'jpeg', 'gif'] + const typesVideo = ['mp4', 'm4v', 'mov', 'webm'] + const { mimeType, data } = d + if (mimeType.startsWith('image/')) { + if (!typesImage.includes(mimeType.split('/')[1])) { + console.warn( + 'Image type not supported:', + mimeType.split('/')[1] + ) + return + } + images.push({ type: mimeType.split('/')[1], uri: data }) + } else if (mimeType.startsWith('video/')) { + if (!typesVideo.includes(mimeType.split('/')[1])) { + console.warn( + 'Video type not supported:', + mimeType.split('/')[1] + ) + return + } + video = { type: mimeType.split('/')[1], uri: data } + } else { + if (typesImage.includes(data.split('.').pop() || '')) { + images.push({ type: data.split('.').pop()!, uri: data }) + return + } + if (typesVideo.includes(data.split('.').pop() || '')) { + video = { type: data.split('.').pop()!, uri: data } + return + } + text = !text ? data : text.concat(text, `\n${data}`) + } + }) + break + case 'android': + if (!item.mimeType) { return } - video = { type: mimeType.split('/')[1], uri: data } - } else { - if (typesImage.includes(data.split('.').pop() || '')) { - images.push({ type: data.split('.').pop()!, uri: data }) - return + let tempData: string[] + if (!Array.isArray(item.data)) { + tempData = [item.data] + } else { + tempData = item.data } - if (typesVideo.includes(data.split('.').pop() || '')) { - video = { type: data.split('.').pop()!, uri: data } - return - } - text = !text ? data : text.concat(text, `\n${data}`) - } - }) - navigationRef.navigate('Screen-Compose', { - type: 'share', - text, - images, - video - }) + tempData.forEach(d => { + const typesImage = ['png', 'jpg', 'jpeg', 'gif'] + const typesVideo = ['mp4', 'm4v', 'mov', 'webm', 'mpeg'] + if (item.mimeType!.startsWith('image/')) { + if (!typesImage.includes(item.mimeType.split('/')[1])) { + console.warn( + 'Image type not supported:', + item.mimeType.split('/')[1] + ) + return + } + images.push({ type: item.mimeType.split('/')[1], uri: d }) + } else if (item.mimeType.startsWith('video/')) { + if (!typesVideo.includes(item.mimeType.split('/')[1])) { + console.warn( + 'Video type not supported:', + item.mimeType.split('/')[1] + ) + return + } + video = { type: item.mimeType.split('/')[1], uri: d } + } else { + if (typesImage.includes(d.split('.').pop() || '')) { + images.push({ type: d.split('.').pop()!, uri: d }) + return + } + if (typesVideo.includes(d.split('.').pop() || '')) { + video = { type: d.split('.').pop()!, uri: d } + return + } + text = !text ? d : text.concat(text, `\n${d}`) + } + }) + break + } + + if (!text && (!images || !images.length) && !video) { + return + } else { + navigationRef.navigate('Screen-Compose', { + type: 'share', + text, + images, + video + }) + } }, [instanceActive] ) useEffect(() => { - ShareMenu.getInitialShare(item => handleShare(item)) + ShareMenu.getInitialShare(handleShare) }, []) useEffect(() => { - const listener = ShareMenu.addNewShareListener(item => handleShare(item)) + const listener = ShareMenu.addNewShareListener(handleShare) return () => { listener.remove() } From 8caf31589472575f915f58311fb97a9bd34d8812 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Thu, 5 May 2022 23:08:29 +0200 Subject: [PATCH 02/27] Improve sharing error messaging --- src/Screens.tsx | 28 ++++++++++++++++++++++++++++ src/components/Message.tsx | 2 +- src/i18n/en/screens.json | 4 ++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Screens.tsx b/src/Screens.tsx index 8420c867..6fa2fa60 100644 --- a/src/Screens.tsx +++ b/src/Screens.tsx @@ -197,6 +197,13 @@ const Screens: React.FC = ({ localCorrupt }) => { 'Image type not supported:', mimeType.split('/')[1] ) + displayMessage({ + message: t('shareError.imageNotSupported', { + type: mimeType.split('/')[1] + }), + type: 'error', + theme + }) return } images.push({ type: mimeType.split('/')[1], uri: data }) @@ -206,6 +213,13 @@ const Screens: React.FC = ({ localCorrupt }) => { 'Video type not supported:', mimeType.split('/')[1] ) + displayMessage({ + message: t('shareError.videoNotSupported', { + type: mimeType.split('/')[1] + }), + type: 'error', + theme + }) return } video = { type: mimeType.split('/')[1], uri: data } @@ -241,6 +255,13 @@ const Screens: React.FC = ({ localCorrupt }) => { 'Image type not supported:', item.mimeType.split('/')[1] ) + displayMessage({ + message: t('shareError.imageNotSupported', { + type: item.mimeType.split('/')[1] + }), + type: 'error', + theme + }) return } images.push({ type: item.mimeType.split('/')[1], uri: d }) @@ -250,6 +271,13 @@ const Screens: React.FC = ({ localCorrupt }) => { 'Video type not supported:', item.mimeType.split('/')[1] ) + displayMessage({ + message: t('shareError.videoNotSupported', { + type: item.mimeType.split('/')[1] + }), + type: 'error', + theme + }) return } video = { type: item.mimeType.split('/')[1], uri: d } diff --git a/src/components/Message.tsx b/src/components/Message.tsx index ab6c1468..4fb35c62 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -59,7 +59,7 @@ const displayMessage = ({ if (ref) { ref.current?.showMessage({ - duration: type === 'error' ? 5000 : duration === 'short' ? 1500 : 3000, + duration: type === 'error' ? 8000 : duration === 'short' ? 3000 : 5000, autoHide, message, description, diff --git a/src/i18n/en/screens.json b/src/i18n/en/screens.json index 2de2cc7a..b60b9e98 100644 --- a/src/i18n/en/screens.json +++ b/src/i18n/en/screens.json @@ -10,5 +10,9 @@ "pushError": { "message": "Push service error", "description": "Please re-enable push notification in settings" + }, + "shareError": { + "imageNotSupported": "Image type {{type}} not supported", + "videoNotSupported": "Video type {{type}} not supported" } } \ No newline at end of file From 7c48c61c999f18b618aec5c02d0d3b30a265ec24 Mon Sep 17 00:00:00 2001 From: Zhiyuan Zheng Date: Sat, 7 May 2022 00:52:32 +0200 Subject: [PATCH 03/27] Using new text component Need to use global accessibility checks rather than per text component which is not efficient --- fastlane/Fastfile | 2 +- package.json | 2 +- src/components/Account.tsx | 50 ++-- src/components/Button.tsx | 17 +- src/components/Emojis/List.tsx | 46 ++-- src/components/Hashtag.tsx | 18 +- src/components/Header/Center.tsx | 20 +- src/components/Header/Left.tsx | 57 ++--- src/components/Header/Right.tsx | 72 ++---- src/components/Input.tsx | 80 +++--- src/components/Instance.tsx | 142 +++++------ src/components/Instance/Info.tsx | 53 ++-- src/components/Menu/Header.tsx | 23 +- src/components/Menu/Row.tsx | 30 +-- src/components/Parse/Emojis.tsx | 21 +- src/components/Parse/HTML.tsx | 19 +- src/components/Text.tsx | 76 ++++++ src/components/Timeline/Empty.tsx | 33 ++- src/components/Timeline/Footer.tsx | 9 +- src/components/Timeline/Lookback.tsx | 12 +- src/components/Timeline/Refresh.tsx | 65 ++--- src/components/Timeline/Shared/Actions.tsx | 20 +- .../Shared/Attachment/Unsupported.tsx | 50 ++-- src/components/Timeline/Shared/Card.tsx | 83 +++--- src/components/Timeline/Shared/Feedback.tsx | 15 +- src/components/Timeline/Shared/Filtered.tsx | 9 +- .../Timeline/Shared/FullConversation.tsx | 8 +- .../Timeline/Shared/HeaderConversation.tsx | 13 +- .../Timeline/Shared/HeaderShared/Account.tsx | 30 +-- .../Shared/HeaderShared/Application.tsx | 21 +- .../Timeline/Shared/HeaderShared/Created.tsx | 10 +- src/components/Timeline/Shared/Poll.tsx | 152 +++++------ src/components/Timeline/Shared/Translate.tsx | 13 +- src/i18n/en/screens/compose.json | 3 +- src/screens/Announcements.tsx | 240 ++++++++---------- src/screens/Compose/DraftsList/Root.tsx | 125 +++++---- src/screens/Compose/EditAttachment/Image.tsx | 23 +- src/screens/Compose/EditAttachment/Root.tsx | 65 +++-- .../Compose/Root/Footer/Attachments.tsx | 168 ++++++------ src/screens/Compose/Root/Footer/Emojis.tsx | 68 ++--- src/screens/Compose/Root/Footer/Poll.tsx | 91 +++---- src/screens/Compose/Root/Header/PostingAs.tsx | 13 +- .../Compose/Root/Header/SpoilerInput.tsx | 35 +-- src/screens/Compose/Root/Header/TextInput.tsx | 31 +-- src/screens/Compose/formatText.tsx | 6 +- src/screens/Tabs/Me/Profile/Fields.tsx | 37 ++- src/screens/Tabs/Me/Push.tsx | 12 +- src/screens/Tabs/Me/Settings/Analytics.tsx | 14 +- src/screens/Tabs/Me/Settings/Dev.tsx | 9 +- src/screens/Tabs/Me/SettingsFontsize.tsx | 115 +++++---- src/screens/Tabs/Me/Switch.tsx | 82 +++--- .../Shared/Account/Information/Account.tsx | 65 ++--- .../Shared/Account/Information/Created.tsx | 33 +-- .../Tabs/Shared/Account/Information/Name.tsx | 29 +-- .../Tabs/Shared/Account/Information/Stats.tsx | 9 +- src/screens/Tabs/Shared/Account/Nav.tsx | 46 ++-- src/screens/Tabs/Shared/History.tsx | 7 +- src/screens/Tabs/Shared/Root.tsx | 54 ++-- src/screens/Tabs/Shared/Search.tsx | 139 +++++----- src/utils/styles/constants.ts | 6 +- 60 files changed, 1302 insertions(+), 1494 deletions(-) create mode 100644 src/components/Text.tsx diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7a7cb227..57f13142 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -123,7 +123,7 @@ private_lane :build_ios do end desc "Build and deploy Android app" -private_lane :build_android do +lane :build_android do sh("echo #{ENV["ANDROID_KEYSTORE"]} | base64 -d | tee #{File.expand_path('..', Dir.pwd)}/android/tooot.jks >/dev/null", log: false) case ENVIRONMENT diff --git a/package.json b/package.json index 3e8a9733..4334f04e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "android": "react-native run-android", "iphone": "react-native run-ios", "ipad": "react-native run-ios --simulator 'iPad mini (6th generation)'", - "app:build": "bundle exec fastlane build", + "app:build": "bundle exec fastlane build_android", "release": "scripts/release.sh", "clean": "react-native-clean-project", "postinstall": "patch-package" diff --git a/src/components/Account.tsx b/src/components/Account.tsx index 7dff9669..b3415253 100644 --- a/src/components/Account.tsx +++ b/src/components/Account.tsx @@ -5,9 +5,10 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback } from 'react' -import { Pressable, StyleSheet, Text, View } from 'react-native' +import { Pressable, View } from 'react-native' import analytics from './analytics' import GracefullyImage from './GracefullyImage' +import CustomText from './Text' export interface Props { account: Mastodon.Account @@ -32,50 +33,45 @@ const ComponentAccount: React.FC = ({ return ( - + - - + @{account.acct} - + ) } -const styles = StyleSheet.create({ - itemDefault: { - paddingHorizontal: StyleConstants.Spacing.Global.PagePadding, - paddingVertical: StyleConstants.Spacing.M - }, - itemAccount: { - flexDirection: 'row', - alignItems: 'center' - }, - itemAccountAvatar: { - alignSelf: 'flex-start', - width: StyleConstants.Avatar.S, - height: StyleConstants.Avatar.S, - borderRadius: 6, - marginRight: StyleConstants.Spacing.S - }, - itemAccountAcct: { marginTop: StyleConstants.Spacing.XS } -}) - export default ComponentAccount diff --git a/src/components/Button.tsx b/src/components/Button.tsx index ffdb695f..579134b7 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -7,12 +7,11 @@ import { AccessibilityProps, Pressable, StyleProp, - StyleSheet, - Text, View, ViewStyle } from 'react-native' import { Flow } from 'react-native-animated-spinkit' +import CustomText from './Text' export interface Props { accessibilityLabel?: AccessibilityProps['accessibilityLabel'] @@ -116,7 +115,7 @@ const Button: React.FC = ({ case 'text': return ( <> - = ({ busy: loading }} style={[ - styles.button, { + borderRadius: 100, + justifyContent: 'center', + alignItems: 'center', borderWidth: overlay ? 0 : 1, borderColor: mainColor, backgroundColor: colorBackground, @@ -170,12 +171,4 @@ const Button: React.FC = ({ ) } -const styles = StyleSheet.create({ - button: { - borderRadius: 100, - justifyContent: 'center', - alignItems: 'center' - } -}) - export default Button diff --git a/src/components/Emojis/List.tsx b/src/components/Emojis/List.tsx index df213f1c..48eb8ba3 100644 --- a/src/components/Emojis/List.tsx +++ b/src/components/Emojis/List.tsx @@ -1,3 +1,4 @@ +import CustomText from '@components/Text' import { useAppDispatch } from '@root/store' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { countInstanceEmoji } from '@utils/slices/instancesSlice' @@ -11,8 +12,6 @@ import { findNodeHandle, Pressable, SectionList, - StyleSheet, - Text, View } from 'react-native' import FastImage from 'react-native-fast-image' @@ -30,7 +29,12 @@ const EmojisList = React.memo( const listHeader = useCallback( ({ section: { title } }) => ( - {title} + + {title} + ), [] ) @@ -38,7 +42,15 @@ const EmojisList = React.memo( const listItem = useCallback( ({ index, item }: { item: Mastodon.Emoji[]; index: number }) => { return ( - + {item.map(emoji => { const uri = reduceMotionEnabled ? emoji.static_url : emoji.url if (validUrl.isHttpsUri(uri)) { @@ -64,7 +76,12 @@ const EmojisList = React.memo( 'screenCompose:content.root.footer.emojis.accessibilityHint' )} source={{ uri }} - style={styles.emoji} + style={{ + width: 32, + height: 32, + padding: StyleConstants.Spacing.S, + margin: StyleConstants.Spacing.S + }} /> ) @@ -104,23 +121,4 @@ const EmojisList = React.memo( () => true ) -const styles = StyleSheet.create({ - group: { - position: 'absolute', - ...StyleConstants.FontStyle.S - }, - emojis: { - flex: 1, - flexWrap: 'wrap', - marginTop: StyleConstants.Spacing.M, - marginRight: StyleConstants.Spacing.S - }, - emoji: { - width: 32, - height: 32, - padding: StyleConstants.Spacing.S, - margin: StyleConstants.Spacing.S - } -}) - export default EmojisList diff --git a/src/components/Hashtag.tsx b/src/components/Hashtag.tsx index fede3ab9..7efccdf3 100644 --- a/src/components/Hashtag.tsx +++ b/src/components/Hashtag.tsx @@ -4,8 +4,9 @@ import { TabLocalStackParamList } from '@utils/navigation/navigators' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback } from 'react' -import { Pressable, StyleSheet, Text } from 'react-native' +import { Pressable } from 'react-native' import analytics from './analytics' +import CustomText from './Text' export interface Props { hashtag: Mastodon.Tag @@ -30,23 +31,14 @@ const ComponentHashtag: React.FC = ({ return ( - + #{hashtag.name} - + ) } -const styles = StyleSheet.create({ - itemDefault: { - padding: StyleConstants.Spacing.S * 1.5 - }, - itemHashtag: { - ...StyleConstants.FontStyle.M - } -}) - export default ComponentHashtag diff --git a/src/components/Header/Center.tsx b/src/components/Header/Center.tsx index eb6965ea..5b8656d3 100644 --- a/src/components/Header/Center.tsx +++ b/src/components/Header/Center.tsx @@ -1,7 +1,7 @@ +import CustomText from '@components/Text' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' -import { StyleSheet, Text } from 'react-native' export interface Props { content: string @@ -14,11 +14,12 @@ const HeaderCenter = React.memo( const { colors } = useTheme() return ( - ) @@ -26,11 +27,4 @@ const HeaderCenter = React.memo( (prev, next) => prev.content === next.content ) -const styles = StyleSheet.create({ - text: { - fontSize: 18, - fontWeight: StyleConstants.Font.Weight.Bold - } -}) - export default HeaderCenter diff --git a/src/components/Header/Left.tsx b/src/components/Header/Left.tsx index 6aabba0d..afb0bb87 100644 --- a/src/components/Header/Left.tsx +++ b/src/components/Header/Left.tsx @@ -1,8 +1,9 @@ import Icon from '@components/Icon' +import CustomText from '@components/Text' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useMemo } from 'react' -import { Pressable, StyleSheet, Text } from 'react-native' +import { Pressable } from 'react-native' export interface Props { type?: 'icon' | 'text' @@ -34,8 +35,9 @@ const HeaderLeft: React.FC = ({ ) case 'text': return ( - ) @@ -46,38 +48,27 @@ const HeaderLeft: React.FC = ({ ) } -const styles = StyleSheet.create({ - base: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center' - }, - text: { - ...StyleConstants.FontStyle.M - } -}) - export default HeaderLeft diff --git a/src/components/Header/Right.tsx b/src/components/Header/Right.tsx index 94ebf1d8..3c833c86 100644 --- a/src/components/Header/Right.tsx +++ b/src/components/Header/Right.tsx @@ -1,14 +1,9 @@ import Icon from '@components/Icon' +import CustomText from '@components/Text' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React, { useMemo } from 'react' -import { - AccessibilityProps, - Pressable, - StyleSheet, - Text, - View -} from 'react-native' +import { AccessibilityProps, Pressable, View } from 'react-native' import { Flow } from 'react-native-animated-spinkit' export interface Props { @@ -72,14 +67,12 @@ const HeaderRight: React.FC = ({ case 'text': return ( <> - {loading && loadingSpinkit} @@ -97,38 +90,27 @@ const HeaderRight: React.FC = ({ onPress={onPress} children={children} disabled={disabled || loading} - style={[ - styles.base, - { - backgroundColor: background - ? colors.backgroundOverlayDefault - : undefined, - minHeight: 44, - minWidth: 44, - marginRight: native - ? -StyleConstants.Spacing.S - : StyleConstants.Spacing.S, - ...(type === 'icon' && { - borderRadius: 100 - }), - ...(type === 'text' && { - paddingHorizontal: StyleConstants.Spacing.S - }) - } - ]} + style={{ + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: background + ? colors.backgroundOverlayDefault + : undefined, + minHeight: 44, + minWidth: 44, + marginRight: native + ? -StyleConstants.Spacing.S + : StyleConstants.Spacing.S, + ...(type === 'icon' && { + borderRadius: 100 + }), + ...(type === 'text' && { + paddingHorizontal: StyleConstants.Spacing.S + }) + }} /> ) } -const styles = StyleSheet.create({ - base: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center' - }, - text: { - ...StyleConstants.FontStyle.M - } -}) - export default HeaderRight diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 123c8165..d3f7f34b 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -9,17 +9,11 @@ import React, { useRef, useState } from 'react' -import { - Platform, - StyleSheet, - Text, - TextInput, - TextInputProps, - View -} from 'react-native' +import { Platform, TextInput, TextInputProps, View } from 'react-native' import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated' import { ComponentEmojis, EmojisButton, EmojisList } from './Emojis' import EmojisContext from './Emojis/helpers/EmojisContext' +import CustomText from './Text' export interface Props { autoFocus?: boolean @@ -106,14 +100,14 @@ const Input: React.FC = ({ maxLength={options?.maxLength} > {({ emojisDispatch }) => ( @@ -124,16 +118,15 @@ const Input: React.FC = ({ setInputFocused(false) emojisDispatch({ type: 'activate', payload: false }) }} - style={[ - styles.textInput, - { - color: colors.primaryDefault, - minHeight: - Platform.OS === 'ios' && multiline - ? StyleConstants.Font.LineHeight.M * 5 - : undefined - } - ]} + style={{ + flex: 1, + fontSize: StyleConstants.Font.Size.M, + color: colors.primaryDefault, + minHeight: + Platform.OS === 'ios' && multiline + ? StyleConstants.Font.LineHeight.M * 5 + : undefined + }} onChangeText={setValue} onSelectionChange={onSelectionChange} value={value} @@ -149,16 +142,25 @@ const Input: React.FC = ({ {title ? ( {title} ) : null} {options?.maxLength && value?.length ? ( - + {value?.length} / {options.maxLength} - + ) : null} {inputFocused ? : null} @@ -168,24 +170,4 @@ const Input: React.FC = ({ ) } -const styles = StyleSheet.create({ - base: { - alignItems: 'flex-end', - borderWidth: 1, - marginVertical: StyleConstants.Spacing.S, - padding: StyleConstants.Spacing.S - }, - title: { - position: 'absolute' - }, - textInput: { - flex: 1, - fontSize: StyleConstants.Font.Size.M - }, - maxLength: { - ...StyleConstants.FontStyle.S, - paddingLeft: StyleConstants.Spacing.XS - } -}) - export default Input diff --git a/src/components/Instance.tsx b/src/components/Instance.tsx index 868929c0..5e8c1c75 100644 --- a/src/components/Instance.tsx +++ b/src/components/Instance.tsx @@ -15,8 +15,6 @@ import { Image, KeyboardAvoidingView, Platform, - StyleSheet, - Text, TextInput, View } from 'react-native' @@ -26,6 +24,7 @@ import { Placeholder } from 'rn-placeholder' import analytics from './analytics' import InstanceAuth from './Instance/Auth' import InstanceInfo from './Instance/Info' +import CustomText from './Text' export interface Props { scrollViewRef?: RefObject @@ -134,40 +133,50 @@ const ComponentInstance: React.FC = ({ behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > {!disableHeaderImage ? ( - + ) : null} - - + + = ({ content={instanceQuery.data?.title || undefined} potentialWidth={2} /> - + = ({ potentialWidth={4} /> = ({ potentialWidth={4} /> = ({ /> - + - { if (screenReaderEnabled) { @@ -254,7 +276,7 @@ const ComponentInstance: React.FC = ({ }} > {t('server.disclaimer.base')} - { @@ -265,8 +287,8 @@ const ComponentInstance: React.FC = ({ }} > {t('server.disclaimer.privacy')} - - + + @@ -276,54 +298,4 @@ const ComponentInstance: React.FC = ({ ) } -const styles = StyleSheet.create({ - imageContainer: { flexDirection: 'row' }, - image: { resizeMode: 'contain', flex: 1, aspectRatio: 16 / 9 }, - base: { - marginTop: StyleConstants.Spacing.L, - marginHorizontal: StyleConstants.Spacing.Global.PagePadding - }, - inputRow: { - flexDirection: 'row', - marginHorizontal: StyleConstants.Spacing.Global.PagePadding - }, - prefix: { - borderBottomWidth: 1, - ...StyleConstants.FontStyle.M - }, - textInput: { - flex: 1, - borderBottomWidth: 1, - ...StyleConstants.FontStyle.M, - marginRight: StyleConstants.Spacing.M - }, - instanceStats: { - flex: 1, - flexDirection: 'row' - }, - stat1: { - alignItems: 'flex-start' - }, - stat2: { - alignItems: 'center' - }, - stat3: { - alignItems: 'flex-end' - }, - disclaimer: { - flexDirection: 'row', - marginHorizontal: StyleConstants.Spacing.Global.PagePadding, - marginVertical: StyleConstants.Spacing.M - }, - disclaimerIcon: { - marginTop: - (StyleConstants.Font.LineHeight.S - StyleConstants.Font.Size.S) / 2, - marginRight: StyleConstants.Spacing.XS - }, - disclaimerText: { - flex: 1, - ...StyleConstants.FontStyle.S - } -}) - export default ComponentInstance diff --git a/src/components/Instance/Info.tsx b/src/components/Instance/Info.tsx index 7a2ca6b1..cc985bd5 100644 --- a/src/components/Instance/Info.tsx +++ b/src/components/Instance/Info.tsx @@ -1,7 +1,8 @@ +import CustomText from '@components/Text' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' -import { StyleSheet, Text, View, ViewStyle } from 'react-native' +import { View, ViewStyle } from 'react-native' import { PlaceholderLine } from 'rn-placeholder' export interface Props { @@ -16,14 +17,33 @@ const InstanceInfo = React.memo( const { colors } = useTheme() return ( - - - {header} - + + {content ? ( - - {content} - + ) : ( prev.content === next.content ) -const styles = StyleSheet.create({ - base: { - flex: 1, - marginTop: StyleConstants.Spacing.M, - paddingLeft: StyleConstants.Spacing.Global.PagePadding, - paddingRight: StyleConstants.Spacing.Global.PagePadding - }, - header: { - ...StyleConstants.FontStyle.S, - fontWeight: StyleConstants.Font.Weight.Bold, - marginBottom: StyleConstants.Spacing.XS - }, - content: { - ...StyleConstants.FontStyle.M - } -}) - export default InstanceInfo diff --git a/src/components/Menu/Header.tsx b/src/components/Menu/Header.tsx index fca2e036..9b2077f1 100644 --- a/src/components/Menu/Header.tsx +++ b/src/components/Menu/Header.tsx @@ -1,7 +1,8 @@ import React from 'react' -import { StyleSheet, Text, View } from 'react-native' +import { View } from 'react-native' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' +import CustomText from '@components/Text' export interface Props { heading: string @@ -11,20 +12,16 @@ const MenuHeader: React.FC = ({ heading }) => { const { colors } = useTheme() return ( - - {heading} + + + {heading} + ) } -const styles = StyleSheet.create({ - base: { - paddingBottom: StyleConstants.Spacing.S - }, - text: { - ...StyleConstants.FontStyle.S, - fontWeight: StyleConstants.Font.Weight.Bold - } -}) - export default MenuHeader diff --git a/src/components/Menu/Row.tsx b/src/components/Menu/Row.tsx index b44e68d3..130a7bf3 100644 --- a/src/components/Menu/Row.tsx +++ b/src/components/Menu/Row.tsx @@ -1,4 +1,5 @@ import Icon from '@components/Icon' +import CustomText from '@components/Text' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' @@ -99,12 +100,13 @@ const MenuRow: React.FC = ({ /> ) : null} - {title} - + @@ -112,18 +114,15 @@ const MenuRow: React.FC = ({ {content ? ( typeof content === 'string' ? ( - {content} - + ) : ( content ) @@ -150,9 +149,9 @@ const MenuRow: React.FC = ({ ) : null} {description ? ( - + {description} - + ) : null} @@ -187,9 +186,6 @@ const styles = StyleSheet.create({ main: { flex: 1 }, - title: { - ...StyleConstants.FontStyle.M - }, description: { ...StyleConstants.FontStyle.S }, diff --git a/src/components/Parse/Emojis.tsx b/src/components/Parse/Emojis.tsx index 0a06b02c..58e905b5 100644 --- a/src/components/Parse/Emojis.tsx +++ b/src/components/Parse/Emojis.tsx @@ -1,10 +1,11 @@ +import CustomText from '@components/Text' import { useAccessibility } from '@utils/accessibility/AccessibilityManager' import { getSettingsFontsize } from '@utils/slices/settingsSlice' import { StyleConstants } from '@utils/styles/constants' import { adaptiveScale } from '@utils/styles/scaling' import { useTheme } from '@utils/styles/ThemeManager' import React, { useMemo } from 'react' -import { StyleSheet, Text } from 'react-native' +import { StyleSheet } from 'react-native' import FastImage from 'react-native-fast-image' import { useSelector } from 'react-redux' import validUrl from 'valid-url' @@ -57,7 +58,7 @@ const ParseEmojis = React.memo( }, [theme, adaptiveFontsize]) return ( - + {emojis ? ( content .split(regexEmoji) @@ -69,30 +70,34 @@ const ParseEmojis = React.memo( return emojiShortcode === `:${emoji.shortcode}:` }) if (emojiIndex === -1) { - return {emojiShortcode} + return ( + + {emojiShortcode} + + ) } else { const uri = reduceMotionEnabled ? emojis[emojiIndex].static_url : emojis[emojiIndex].url if (validUrl.isHttpsUri(uri)) { return ( - + {i === 0 ? ' ' : undefined} - + ) } else { return null } } } else { - return {str} + return {str} } }) ) : ( - {content} + {content} )} - + ) }, (prev, next) => prev.content === next.content diff --git a/src/components/Parse/HTML.tsx b/src/components/Parse/HTML.tsx index cb9cb39d..d5a2dac7 100644 --- a/src/components/Parse/HTML.tsx +++ b/src/components/Parse/HTML.tsx @@ -2,6 +2,7 @@ import analytics from '@components/analytics' import Icon from '@components/Icon' import openLink from '@components/openLink' import ParseEmojis from '@components/Parse/Emojis' +import CustomText from '@components/Text' import { useNavigation, useRoute } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' import { TabLocalStackParamList } from '@utils/navigation/navigators' @@ -12,7 +13,7 @@ import { adaptiveScale } from '@utils/styles/scaling' import { useTheme } from '@utils/styles/ThemeManager' import React, { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Pressable, Text, View } from 'react-native' +import { Pressable, View } from 'react-native' import HTMLView from 'react-native-htmlview' import { useSelector } from 'react-redux' @@ -53,7 +54,7 @@ const renderNode = ({ ? routeParams.hashtag !== tag[1] && routeParams.hashtag !== tag[2] : true return ( - {node.children[0].data} {node.children[1]?.children[0].data} - + ) } else if (classes.includes('mention') && mentions) { const accountIndex = mentions.findIndex( @@ -82,7 +83,7 @@ const renderNode = ({ ? routeParams.account.id !== mentions[accountIndex]?.id : true return ( - {node.children[0].data} {node.children[1]?.children[0].data} - + ) } } else { @@ -113,7 +114,7 @@ const renderNode = ({ const shouldBeTag = tags && tags.filter(tag => `#${tag.name}` === content).length > 0 return ( - ) : null} - + ) } break @@ -252,7 +253,7 @@ const ParseHTML = React.memo( return ( - - + fontStyle?: undefined + fontSize?: 'S' | 'M' | 'L' + lineHeight?: 'S' | 'M' | 'L' + fontWeight?: 'Normal' | 'Bold' + } + | { + style?: Omit + fontStyle: 'S' | 'M' | 'L' + fontSize?: undefined + lineHeight?: undefined + fontWeight?: 'Normal' | 'Bold' + } + +const CustomText: React.FC = ({ + children, + style, + fontStyle, + fontSize, + fontWeight = 'Normal', + lineHeight, + ...rest +}) => { + const [isBoldText, setIsBoldText] = useState(false) + useEffect(() => { + const boldTextChangedSubscription = AccessibilityInfo.addEventListener( + 'boldTextChanged', + boldTextChanged => { + setIsBoldText(boldTextChanged) + } + ) + + AccessibilityInfo.isBoldTextEnabled().then(boldTextEnabled => { + setIsBoldText(boldTextEnabled) + }) + + return () => { + boldTextChangedSubscription.remove() + } + }, []) + enum BoldMapping { + 'Normal' = '600', + 'Bold' = '800' + } + + return ( + + {children} + + ) +} + +export default CustomText diff --git a/src/components/Timeline/Empty.tsx b/src/components/Timeline/Empty.tsx index 145db777..af5227e5 100644 --- a/src/components/Timeline/Empty.tsx +++ b/src/components/Timeline/Empty.tsx @@ -1,12 +1,13 @@ import analytics from '@components/analytics' import Button from '@components/Button' import Icon from '@components/Icon' +import CustomText from '@components/Text' import { QueryKeyTimeline, useTimelineQuery } from '@utils/queryHooks/timeline' import { StyleConstants } from '@utils/styles/constants' import { useTheme } from '@utils/styles/ThemeManager' import React from 'react' import { useTranslation } from 'react-i18next' -import { StyleSheet, Text, View } from 'react-native' +import { View } from 'react-native' import { Circle } from 'react-native-animated-spinkit' export interface Props { @@ -40,9 +41,16 @@ const TimelineEmpty = React.memo( size={StyleConstants.Font.Size.L} color={colors.primaryDefault} /> - + {t('empty.error.message')} - +