A lot of updates

This commit is contained in:
Zhiyuan Zheng 2020-11-28 17:07:30 +01:00
parent 8200375c92
commit 735cc0b903
No known key found for this signature in database
GPG Key ID: 078A93AB607D85E0
29 changed files with 940 additions and 1275 deletions

12
App.tsx
View File

@ -15,10 +15,14 @@ setConsole({
error: console.warn
})
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
// whyDidYouRender(React)
}
// if (__DEV__) {
// const whyDidYouRender = require('@welldone-software/why-did-you-render')
// whyDidYouRender(React, {
// trackAllPureComponents: true,
// trackHooks: true,
// hotReloadBufferMs: 1000
// })
// }
const App: React.FC = () => {
return (

View File

@ -53,7 +53,6 @@
"@types/react": "~16.9.35",
"@types/react-dom": "^16.9.9",
"@types/react-native": "~0.63.2",
"@types/react-native-htmlview": "^0.12.2",
"@types/react-navigation": "^3.4.0",
"@types/react-redux": "^7.1.11",
"@welldone-software/why-did-you-render": "^6.0.0-rc.1",
@ -61,4 +60,4 @@
"typescript": "~3.9.2"
},
"private": true
}
}

1
src/@types/untyped.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'react-native-toast-message'

View File

@ -4,10 +4,8 @@ import { NavigationContainer } from '@react-navigation/native'
import { enableScreens } from 'react-native-screens'
import React from 'react'
import { Feather } from '@expo/vector-icons'
// @ts-ignore
import Toast from 'react-native-toast-message'
import { Feather } from '@expo/vector-icons'
import ScreenLocal from 'src/screens/Local'
import ScreenPublic from 'src/screens/Public'
@ -17,6 +15,7 @@ import ScreenMe from 'src/screens/Me'
import { themes } from 'src/utils/styles/themes'
import { useTheme } from 'src/utils/styles/ThemeManager'
import getCurrentTab from 'src/utils/getCurrentTab'
import { toastConfig } from 'src/components/toast'
enableScreens()
const Tab = createBottomTabNavigator<RootStackParamList>()
@ -89,7 +88,7 @@ export const Index: React.FC = () => {
<Tab.Screen name='Screen-Me' component={ScreenMe} />
</Tab.Navigator>
<Toast ref={(ref: any) => Toast.setRef(ref)} />
<Toast ref={(ref: any) => Toast.setRef(ref)} config={toastConfig} />
</NavigationContainer>
)
}

View File

@ -45,9 +45,8 @@ const client = async ({
// } catch (error) {
// return Promise.reject('ky error: ' + error.json())
// }
console.log('upload done')
if (response?.ok) {
console.log('returning ok')
console.log('Query: /' + endpoint)
if (response.ok) {
return Promise.resolve({
headers: response.headers,
body: await response.json()

View File

@ -0,0 +1,128 @@
import React, { useEffect, useRef } from 'react'
import {
Animated,
Dimensions,
Modal,
PanResponder,
Pressable,
StyleSheet,
Text,
View
} from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTheme } from 'src/utils/styles/ThemeManager'
import constants from 'src/utils/styles/constants'
export interface Props {
children: React.ReactNode
visible: boolean
handleDismiss: () => void
}
const BottomSheet: React.FC<Props> = ({ children, visible, handleDismiss }) => {
const { theme } = useTheme()
const insets = useSafeAreaInsets()
const panY = useRef(new Animated.Value(Dimensions.get('screen').height))
.current
const top = panY.interpolate({
inputRange: [-1, 0, 1],
outputRange: [0, 0, 1]
})
const resetModal = Animated.timing(panY, {
toValue: 0,
duration: 300,
useNativeDriver: false
})
const closeModal = Animated.timing(panY, {
toValue: Dimensions.get('screen').height,
duration: 350,
useNativeDriver: false
})
const panResponder = useRef(
PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event([null, { dy: panY }], {
useNativeDriver: false
}),
onPanResponderRelease: (e, gs) => {
if (gs.dy > 0 && gs.vy > 1) {
return closeModal.start(() => handleDismiss())
} else if (gs.dy === 0 && gs.vy === 0) {
return closeModal.start(() => handleDismiss())
}
return resetModal.start()
}
})
).current
useEffect(() => {
if (visible) {
resetModal.start()
}
}, [visible])
return (
<Modal animated animationType='fade' visible={visible} transparent>
<View
style={[styles.overlay, { backgroundColor: theme.border }]}
{...panResponder.panHandlers}
>
<Animated.View
style={[
styles.container,
{
top,
backgroundColor: theme.background,
paddingBottom: insets.bottom
}
]}
>
<View
style={[styles.handle, { backgroundColor: theme.background }]}
/>
{children}
<Pressable
onPress={() => closeModal.start(() => handleDismiss())}
style={[styles.cancel, { borderColor: theme.primary }]}
>
<Text style={[styles.text, { color: theme.primary }]}></Text>
</Pressable>
</Animated.View>
</View>
</Modal>
)
}
const styles = StyleSheet.create({
overlay: {
flex: 1,
justifyContent: 'flex-end'
},
container: {
padding: constants.SPACING_L,
paddingTop: constants.SPACING_M
},
handle: {
alignSelf: 'center',
width: constants.GLOBAL_SPACING_BASE * 8,
height: constants.GLOBAL_SPACING_BASE / 2,
borderRadius: 100,
top: -constants.SPACING_M * 2
},
cancel: {
padding: constants.SPACING_S,
borderWidth: 1,
borderRadius: 100
},
text: {
fontSize: constants.FONT_SIZE_L,
textAlign: 'center'
}
})
export default BottomSheet

View File

@ -0,0 +1,37 @@
import React from 'react'
import { Pressable, StyleSheet, Text } from 'react-native'
import { Feather } from '@expo/vector-icons'
import constants from 'src/utils/styles/constants'
import { useTheme } from 'src/utils/styles/ThemeManager'
export interface Props {
onPressFunction: () => void
icon: string
text: string
}
const BottomSheetRow: React.FC<Props> = ({ onPressFunction, icon, text }) => {
const { theme } = useTheme()
return (
<Pressable onPress={() => onPressFunction()} style={styles.pressable}>
<Feather name={icon} color={theme.primary} size={constants.FONT_SIZE_L} />
<Text style={[styles.text, { color: theme.primary }]}>{text}</Text>
</Pressable>
)
}
const styles = StyleSheet.create({
pressable: {
flexDirection: 'row',
marginBottom: constants.SPACING_L
},
text: {
fontSize: constants.FONT_SIZE_M,
lineHeight: constants.FONT_SIZE_L,
marginLeft: constants.SPACING_S
}
})
export default BottomSheetRow

View File

@ -28,7 +28,7 @@ const Core: React.FC<Props> = ({ text, destructive = false }) => {
return (
<View style={styles.core}>
<Text style={{ color: destructive ? theme.dangerous : theme.primary }}>
<Text style={{ color: destructive ? theme.error : theme.primary }}>
{text}
</Text>
</View>

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback } from 'react'
import { Text } from 'react-native'
import HTMLView, { HTMLViewNode } from 'react-native-htmlview'
import { useNavigation } from '@react-navigation/native'
@ -102,22 +102,30 @@ const ParseContent: React.FC<Props> = ({
const navigation = useNavigation()
const { theme } = useTheme()
const renderNodeCallback = useCallback(
(node, index) =>
renderNode({ theme, node, index, navigation, mentions, showFullLink }),
[]
)
const textComponent = useCallback(
({ children }) =>
emojis && children ? (
<Emojis content={children.toString()} emojis={emojis} size={size} />
) : (
<Text>{children}</Text>
),
[]
)
const rootComponent = useCallback(({ children }) => {
return <Text numberOfLines={linesTruncated}>{children}</Text>
}, [])
return (
<HTMLView
value={content}
renderNode={(node, index) =>
renderNode({ theme, node, index, navigation, mentions, showFullLink })
}
TextComponent={({ children }) =>
emojis && children ? (
<Emojis content={children.toString()} emojis={emojis} size={size} />
) : (
<Text>{children}</Text>
)
}
RootComponent={({ children }) => {
return <Text numberOfLines={linesTruncated}>{children}</Text>
}}
TextComponent={textComponent}
RootComponent={rootComponent}
renderNode={renderNodeCallback}
/>
)
}

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from 'react'
import { Dimensions, FlatList, Text, View } from 'react-native'
import React, { useCallback, useEffect, useRef, useState } from 'react'
import { Dimensions, FlatList, StyleSheet, Text, View } from 'react-native'
import SegmentedControl from '@react-native-community/segmented-control'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useSelector } from 'react-redux'
@ -59,6 +59,52 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
const horizontalPaging = useRef<FlatList>(null!)
const onChangeSegment = useCallback(({ nativeEvent }) => {
setSegmentManuallyTriggered(true)
setSegment(nativeEvent.selectedSegmentIndex)
horizontalPaging.current.scrollToIndex({
index: nativeEvent.selectedSegmentIndex
})
}, [])
const onPressSearch = useCallback(() => {
navigation.navigate(getCurrentTab(navigation), {
screen: 'Screen-Shared-Search'
})
}, [])
const flGetItemLayout = useCallback(
(data, index) => ({
length: Dimensions.get('window').width,
offset: Dimensions.get('window').width * index,
index
}),
[]
)
const flKeyExtrator = useCallback(({ page }) => page, [])
const flRenderItem = useCallback(
({ item, index }) => {
if (!localRegistered && index === 0) {
return null
}
return <Page item={item} localRegistered={localRegistered} />
},
[localRegistered]
)
const flOnMomentumScrollEnd = useCallback(
() => setSegmentManuallyTriggered(false),
[]
)
const flOnScroll = useCallback(
({ nativeEvent }) =>
!segmentManuallyTriggered &&
setSegment(
nativeEvent.contentOffset.x <= Dimensions.get('window').width / 2
? 0
: 1
),
[]
)
return (
<Stack.Navigator>
<Stack.Screen
@ -71,13 +117,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
<SegmentedControl
values={[content[0].title, content[1].title]}
selectedIndex={segment}
onChange={({ nativeEvent }) => {
setSegmentManuallyTriggered(true)
setSegment(nativeEvent.selectedSegmentIndex)
horizontalPaging.current.scrollToIndex({
index: nativeEvent.selectedSegmentIndex
})
}}
onChange={onChangeSegment}
style={{ width: 150, height: 30 }}
/>
),
@ -86,11 +126,7 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
name='search'
size={24}
color={theme.secondary}
onPress={() => {
navigation.navigate(getCurrentTab(navigation), {
screen: 'Screen-Shared-Search'
})
}}
onPress={onPressSearch}
/>
)
})
@ -99,42 +135,19 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
{() => {
return (
<FlatList
style={{ width: Dimensions.get('window').width, height: '100%' }}
data={content}
extraData={localRegistered}
keyExtractor={({ page }) => page}
renderItem={({ item, index }) => {
if (!localRegistered && index === 0) {
return null
}
return (
<Page
key={index}
item={item}
localRegistered={localRegistered}
/>
)
}}
ref={horizontalPaging}
bounces={false}
getItemLayout={(data, index) => ({
length: Dimensions.get('window').width,
offset: Dimensions.get('window').width * index,
index
})}
horizontal
onMomentumScrollEnd={() => setSegmentManuallyTriggered(false)}
onScroll={({ nativeEvent }) =>
!segmentManuallyTriggered &&
setSegment(
nativeEvent.contentOffset.x <=
Dimensions.get('window').width / 2
? 0
: 1
)
}
pagingEnabled
data={content}
bounces={false}
onScroll={flOnScroll}
ref={horizontalPaging}
style={styles.flatList}
renderItem={flRenderItem}
extraData={localRegistered}
keyExtractor={flKeyExtrator}
getItemLayout={flGetItemLayout}
showsHorizontalScrollIndicator={false}
onMomentumScrollEnd={flOnMomentumScrollEnd}
/>
)
}}
@ -145,4 +158,11 @@ const Timelines: React.FC<Props> = ({ name, content }) => {
)
}
export default Timelines
const styles = StyleSheet.create({
flatList: {
width: Dimensions.get('window').width,
height: '100%'
}
})
export default React.memo(Timelines, () => true)

View File

@ -1,5 +1,12 @@
import React from 'react'
import { ActivityIndicator, AppState, FlatList, Text, View } from 'react-native'
import React, { useCallback } from 'react'
import {
ActivityIndicator,
AppState,
FlatList,
StyleSheet,
Text,
View
} from 'react-native'
import { setFocusHandler, useInfiniteQuery } from 'react-query'
import TimelineNotifications from 'src/components/Timelines/Timeline/Notifications'
@ -51,6 +58,40 @@ const Timeline: React.FC<Props> = ({
const flattenData = data ? data.flatMap(d => [...d?.toots]) : []
// const flattenPointer = data ? data.flatMap(d => [d?.pointer]) : []
const flKeyExtrator = useCallback(({ id }) => id, [])
const flRenderItem = useCallback(({ item }) => {
switch (page) {
case 'Conversations':
return <TimelineConversation item={item} />
case 'Notifications':
return <TimelineNotifications notification={item} queryKey={queryKey} />
default:
return <TimelineDefault item={item} queryKey={queryKey} />
}
}, [])
const flItemSeparatorComponent = useCallback(() => <TimelineSeparator />, [])
const flOnRefresh = useCallback(
() =>
!disableRefresh &&
fetchMore(
{
direction: 'prev',
id: flattenData[0].id
},
{ previous: true }
),
[disableRefresh]
)
const flOnEndReach = useCallback(
() =>
!disableRefresh &&
fetchMore({
direction: 'next',
id: flattenData[flattenData.length - 1].id
}),
[disableRefresh]
)
let content
if (!isSuccess) {
content = <ActivityIndicator />
@ -60,53 +101,18 @@ const Timeline: React.FC<Props> = ({
content = (
<>
<FlatList
style={{ minHeight: '100%' }}
scrollEnabled={scrollEnabled} // For timeline in Account view
data={flattenData}
keyExtractor={({ id }) => id}
renderItem={({ item, index, separators }) => {
switch (page) {
case 'Conversations':
return <TimelineConversation key={index} item={item} />
case 'Notifications':
return (
<TimelineNotifications
key={index}
notification={item}
queryKey={queryKey}
/>
)
default:
return (
<TimelineDefault
key={index}
item={item}
queryKey={queryKey}
/>
)
}
}}
ItemSeparatorComponent={() => <TimelineSeparator />}
onRefresh={flOnRefresh}
renderItem={flRenderItem}
onEndReached={flOnEndReach}
keyExtractor={flKeyExtrator}
style={styles.flatList}
scrollEnabled={scrollEnabled} // For timeline in Account view
ItemSeparatorComponent={flItemSeparatorComponent}
refreshing={!disableRefresh && isLoading}
onEndReachedThreshold={!disableRefresh ? 0.5 : null}
// require getItemLayout
// {...(flattenPointer[0] && { initialScrollIndex: flattenPointer[0] })}
{...(!disableRefresh && {
onRefresh: () =>
fetchMore(
{
direction: 'prev',
id: flattenData[0].id
},
{ previous: true }
),
refreshing: isLoading,
onEndReached: () => {
fetchMore({
direction: 'next',
id: flattenData[flattenData.length - 1].id
})
},
onEndReachedThreshold: 0.5
})}
/>
{isFetchingMore && <ActivityIndicator />}
</>
@ -116,4 +122,10 @@ const Timeline: React.FC<Props> = ({
return <View>{content}</View>
}
const styles = StyleSheet.create({
flatList: {
minHeight: '100%'
}
})
export default Timeline

View File

@ -1,4 +1,4 @@
import React, { useMemo } from 'react'
import React, { useCallback, useMemo } from 'react'
import { Dimensions, Pressable, StyleSheet, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
@ -24,6 +24,43 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
let actualStatus = item.reblog ? item.reblog : item
const pressableToot = useCallback(
() =>
navigation.navigate('Screen-Shared-Toot', {
toot: actualStatus.id
}),
[]
)
const childrenToot = useMemo(
() => (
<>
{actualStatus.content ? (
<Content
content={actualStatus.content}
emojis={actualStatus.emojis}
mentions={actualStatus.mentions}
spoiler_text={actualStatus.spoiler_text}
// tags={actualStatus.tags}
/>
) : (
<></>
)}
{actualStatus.poll && <Poll poll={actualStatus.poll} />}
{actualStatus.media_attachments.length > 0 && (
<Attachment
media_attachments={actualStatus.media_attachments}
sensitive={actualStatus.sensitive}
width={
Dimensions.get('window').width - constants.SPACING_M * 2 - 50 - 8
}
/>
)}
{actualStatus.card && <Card card={actualStatus.card} />}
</>
),
[]
)
const statusView = useMemo(() => {
return (
<View style={styles.statusView}>
@ -54,40 +91,7 @@ const TimelineDefault: React.FC<Props> = ({ item, queryKey }) => {
application={item.application}
/>
{/* Can pass toot info to next page to speed up performance */}
<Pressable
onPress={() =>
navigation.navigate('Screen-Shared-Toot', {
toot: actualStatus.id
})
}
>
{actualStatus.content ? (
<Content
content={actualStatus.content}
emojis={actualStatus.emojis}
mentions={actualStatus.mentions}
spoiler_text={actualStatus.spoiler_text}
// tags={actualStatus.tags}
// style={{ flex: 1 }}
/>
) : (
<></>
)}
{actualStatus.poll && <Poll poll={actualStatus.poll} />}
{actualStatus.media_attachments.length > 0 && (
<Attachment
media_attachments={actualStatus.media_attachments}
sensitive={actualStatus.sensitive}
width={
Dimensions.get('window').width -
constants.SPACING_M * 2 -
50 -
8
}
/>
)}
{actualStatus.card && <Card card={actualStatus.card} />}
</Pressable>
<Pressable onPress={pressableToot} children={childrenToot} />
<ActionsStatus queryKey={queryKey} status={actualStatus} />
</View>
</View>
@ -114,4 +118,12 @@ const styles = StyleSheet.create({
}
})
export default TimelineDefault
export default React.memo(TimelineDefault, (prev, next) => {
let skipUpdate = true
skipUpdate = prev.item.id === next.item.id
skipUpdate = prev.item.replies_count === next.item.replies_count
skipUpdate = prev.item.favourited === next.item.favourited
skipUpdate = prev.item.reblogged === next.item.reblogged
skipUpdate = prev.item.bookmarked === next.item.bookmarked
return skipUpdate
})

View File

@ -108,4 +108,4 @@ const styles = StyleSheet.create({
}
})
export default Actioned
export default React.memo(Actioned)

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import {
ActionSheetIOS,
Clipboard,
@ -11,12 +11,14 @@ import {
import Toast from 'react-native-toast-message'
import { useMutation, useQueryCache } from 'react-query'
import { Feather } from '@expo/vector-icons'
import { findIndex } from 'lodash'
import client from 'src/api/client'
import { getLocalAccountId } from 'src/utils/slices/instancesSlice'
import { store } from 'src/store'
import { useTheme } from 'src/utils/styles/ThemeManager'
import constants from 'src/utils/styles/constants'
import { toast } from 'src/components/toast'
const fireMutation = async ({
id,
@ -25,14 +27,7 @@ const fireMutation = async ({
prevState
}: {
id: string
type:
| 'favourite'
| 'reblog'
| 'bookmark'
| 'mute'
| 'pin'
| 'delete'
| 'account/mute'
type: 'favourite' | 'reblog' | 'bookmark' | 'mute' | 'pin' | 'delete'
stateKey:
| 'favourited'
| 'reblogged'
@ -53,27 +48,13 @@ const fireMutation = async ({
method: 'post',
instance: 'local',
endpoint: `statuses/${id}/${prevState ? 'un' : ''}${type}`
})
}) // bug in response from Mastodon
if (!res.body[stateKey] === prevState) {
if (type === 'bookmark' || 'mute' || 'pin')
Toast.show({
type: 'success',
position: 'bottom',
text1: '功能成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
toast({ type: 'success', content: '功能成功' })
return Promise.resolve(res.body)
} else {
Toast.show({
type: 'error',
position: 'bottom',
text1: '请重试',
autoHide: false,
bottomOffset: 65
})
toast({ type: 'error', content: '功能错误' })
return Promise.reject()
}
break
@ -85,23 +66,10 @@ const fireMutation = async ({
})
if (res.body[stateKey] === id) {
Toast.show({
type: 'success',
position: 'bottom',
text1: '删除成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
toast({ type: 'success', content: '删除成功' })
return Promise.resolve(res.body)
} else {
Toast.show({
type: 'error',
position: 'bottom',
text1: '请重试',
autoHide: false,
bottomOffset: 65
})
toast({ type: 'error', content: '删除失败' })
return Promise.reject()
}
break
@ -120,136 +88,204 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
state ? theme.primary : theme.secondary
const localAccountId = getLocalAccountId(store.getState())
const [modalVisible, setModalVisible] = useState(false)
const [bottomSheetVisible, setBottomSheetVisible] = useState(false)
const queryCache = useQueryCache()
const [mutateAction] = useMutation(fireMutation, {
onMutate: () => {
onMutate: ({ id, type, stateKey, prevState }) => {
queryCache.cancelQueries(queryKey)
const prevData = queryCache.getQueryData(queryKey)
return prevData
},
onSuccess: (newData, params) => {
if (params.type === 'reblog') {
queryCache.invalidateQueries(['Following', { page: 'Following' }])
const oldData = queryCache.getQueryData(queryKey)
switch (type) {
case 'favourite':
case 'reblog':
case 'bookmark':
case 'mute':
case 'pin':
queryCache.setQueryData(queryKey, old =>
(old as {}[]).map((paging: any) => ({
toots: paging.toots.map((toot: any) => {
if (toot.id === id) {
toot[stateKey] =
typeof prevState === 'boolean' ? !prevState : true
}
return toot
}),
pointer: paging.pointer
}))
)
break
case 'delete':
queryCache.setQueryData(queryKey, old =>
(old as {}[]).map((paging: any) => ({
toots: paging.toots.map((toot: any, index: number) => {
if (toot.id === id) {
paging.toots.splice(index, 1)
}
return toot
}),
pointer: paging.pointer
}))
)
break
}
// queryCache.setQueryData(queryKey, (oldData: any) => {
// oldData &&
// oldData.map((paging: any) => {
// paging.toots.map(
// (status: Mastodon.Status | Mastodon.Notification, i: number) => {
// if (status.id === newData.id) {
// paging.toots[i] = newData
// }
// }
// )
// })
// return oldData
// })
return Promise.resolve()
return oldData
},
onError: (err, variables, prevData) => {
queryCache.setQueryData(queryKey, prevData)
},
onSettled: () => {
queryCache.invalidateQueries(queryKey)
onError: (err, _, oldData) => {
toast({ type: 'error', content: '请重试' })
queryCache.setQueryData(queryKey, oldData)
}
})
const onPressReply = useCallback(() => {}, [])
const onPressReblog = useCallback(
() =>
mutateAction({
id: status.id,
type: 'reblog',
stateKey: 'reblogged',
prevState: status.reblogged
}),
[status.reblogged]
)
const onPressFavourite = useCallback(
() =>
mutateAction({
id: status.id,
type: 'favourite',
stateKey: 'favourited',
prevState: status.favourited
}),
[status.favourited]
)
const onPressBookmark = useCallback(
() =>
mutateAction({
id: status.id,
type: 'bookmark',
stateKey: 'bookmarked',
prevState: status.bookmarked
}),
[status.bookmarked]
)
const onPressShare = useCallback(() => setBottomSheetVisible(true), [])
const childrenReply = useMemo(
() => (
<>
<Feather
name='message-circle'
color={iconColor}
size={constants.FONT_SIZE_M + 2}
/>
{status.replies_count > 0 && (
<Text
style={{
color: theme.secondary,
fontSize: constants.FONT_SIZE_M,
marginLeft: constants.SPACING_XS
}}
>
{status.replies_count}
</Text>
)}
</>
),
[status.replies_count]
)
const childrenReblog = useMemo(
() => (
<Feather
name='repeat'
color={
status.visibility === 'public' || status.visibility === 'unlisted'
? iconColorAction(status.reblogged)
: theme.disabled
}
size={constants.FONT_SIZE_M + 2}
/>
),
[status.reblogged]
)
const childrenFavourite = useMemo(
() => (
<Feather
name='heart'
color={iconColorAction(status.favourited)}
size={constants.FONT_SIZE_M + 2}
/>
),
[status.favourited]
)
const childrenBookmark = useMemo(
() => (
<Feather
name='bookmark'
color={iconColorAction(status.bookmarked)}
size={constants.FONT_SIZE_M + 2}
/>
),
[status.bookmarked]
)
const childrenShare = useMemo(
() => (
<Feather
name='share-2'
color={iconColor}
size={constants.FONT_SIZE_M + 2}
/>
),
[]
)
return (
<>
<View style={styles.actions}>
<Pressable style={styles.action}>
<Feather
name='message-circle'
color={iconColor}
size={constants.FONT_SIZE_M + 2}
/>
{status.replies_count > 0 && (
<Text
style={{
color: theme.secondary,
fontSize: constants.FONT_SIZE_M,
marginLeft: constants.SPACING_XS
}}
>
{status.replies_count}
</Text>
)}
</Pressable>
<Pressable
style={styles.action}
onPress={onPressReply}
children={childrenReply}
/>
<Pressable
style={styles.action}
onPress={() =>
mutateAction({
id: status.id,
type: 'reblog',
stateKey: 'reblogged',
prevState: status.reblogged
})
onPress={
status.visibility === 'public' || status.visibility === 'unlisted'
? onPressReblog
: null
}
>
<Feather
name='repeat'
color={iconColorAction(status.reblogged)}
size={constants.FONT_SIZE_M + 2}
/>
</Pressable>
children={childrenReblog}
/>
<Pressable
style={styles.action}
onPress={() =>
mutateAction({
id: status.id,
type: 'favourite',
stateKey: 'favourited',
prevState: status.favourited
})
}
>
<Feather
name='heart'
color={iconColorAction(status.favourited)}
size={constants.FONT_SIZE_M + 2}
/>
</Pressable>
onPress={onPressFavourite}
children={childrenFavourite}
/>
<Pressable
style={styles.action}
onPress={() =>
mutateAction({
id: status.id,
type: 'bookmark',
stateKey: 'bookmarked',
prevState: status.bookmarked
})
}
>
<Feather
name='bookmark'
color={iconColorAction(status.bookmarked)}
size={constants.FONT_SIZE_M + 2}
/>
</Pressable>
onPress={onPressBookmark}
children={childrenBookmark}
/>
<Pressable style={styles.action} onPress={() => setModalVisible(true)}>
<Feather
name='share-2'
color={iconColor}
size={constants.FONT_SIZE_M + 2}
/>
</Pressable>
<Pressable
style={styles.action}
onPress={onPressShare}
children={childrenShare}
/>
</View>
<Modal
animationType='fade'
presentationStyle='overFullScreen'
transparent
visible={modalVisible}
visible={bottomSheetVisible}
>
<Pressable
style={styles.modalBackground}
onPress={() => setModalVisible(false)}
onPress={() => setBottomSheetVisible(false)}
>
<View style={styles.modalSheet}>
<Pressable
@ -266,15 +302,8 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
},
() => {},
() => {
setModalVisible(false)
Toast.show({
type: 'success',
position: 'bottom',
text1: '分享成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
setBottomSheetVisible(false)
toast({ type: 'success', content: '分享成功' })
}
)
}
@ -284,15 +313,8 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
<Pressable
onPress={() => {
Clipboard.setString(status.uri)
setModalVisible(false)
Toast.show({
type: 'success',
position: 'bottom',
text1: '链接复制成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
setBottomSheetVisible(false)
toast({ type: 'success', content: '链接复制成功' })
}}
>
<Text></Text>
@ -300,7 +322,7 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
{status.account.id === localAccountId && (
<Pressable
onPress={() => {
setModalVisible(false)
setBottomSheetVisible(false)
mutateAction({
id: status.id,
type: 'delete',
@ -314,7 +336,7 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
<Text></Text>
<Pressable
onPress={() => {
setModalVisible(false)
setBottomSheetVisible(false)
mutateAction({
id: status.id,
type: 'mute',
@ -325,10 +347,11 @@ const ActionsStatus: React.FC<Props> = ({ queryKey, status }) => {
>
<Text>{status.muted ? '取消静音' : '静音'}</Text>
</Pressable>
{/* Also note that reblogs cannot be pinned. */}
{status.account.id === localAccountId && (
<Pressable
onPress={() => {
setModalVisible(false)
setBottomSheetVisible(false)
mutateAction({
id: status.id,
type: 'pin',

View File

@ -84,4 +84,4 @@ const Attachment: React.FC<Props> = ({
)
}
export default Attachment
export default React.memo(Attachment, () => true)

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback } from 'react'
import { Image, Pressable, StyleSheet } from 'react-native'
import { useNavigation } from '@react-navigation/native'
@ -12,15 +12,14 @@ export interface Props {
const Avatar: React.FC<Props> = ({ uri, id }) => {
const navigation = useNavigation()
// Need to fix go back root
const onPress = useCallback(() => {
navigation.navigate('Screen-Shared-Account', {
id: id
})
}, [])
return (
<Pressable
style={styles.avatar}
onPress={() => {
navigation.navigate('Screen-Shared-Account', {
id: id
})
}}
>
<Pressable style={styles.avatar} onPress={onPress}>
<Image source={{ uri: uri }} style={styles.image} />
</Pressable>
)
@ -39,4 +38,4 @@ const styles = StyleSheet.create({
}
})
export default Avatar
export default React.memo(Avatar, () => true)

View File

@ -1,4 +1,4 @@
import React from 'react'
import React, { useCallback } from 'react'
import { Image, Pressable, StyleSheet, Text, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
@ -8,32 +8,29 @@ export interface Props {
const Card: React.FC<Props> = ({ card }) => {
const navigation = useNavigation()
const onPress = useCallback(() => {
navigation.navigate('Screen-Shared-Webview', {
uri: card.url
})
}, [])
return (
card && (
<Pressable
style={styles.card}
onPress={() => {
navigation.navigate('Webview', {
uri: card.url
})
}}
>
{card.image && (
<View style={styles.left}>
<Image source={{ uri: card.image }} style={styles.image} />
</View>
)}
<View style={styles.right}>
<Text numberOfLines={1}>{card.title}</Text>
{card.description ? (
<Text numberOfLines={2}>{card.description}</Text>
) : (
<></>
)}
<Text numberOfLines={1}>{card.url}</Text>
<Pressable style={styles.card} onPress={onPress}>
{card.image && (
<View style={styles.left}>
<Image source={{ uri: card.image }} style={styles.image} />
</View>
</Pressable>
)
)}
<View style={styles.right}>
<Text numberOfLines={1}>{card.title}</Text>
{card.description ? (
<Text numberOfLines={2}>{card.description}</Text>
) : (
<></>
)}
<Text numberOfLines={1}>{card.url}</Text>
</View>
</Pressable>
)
}
@ -56,4 +53,4 @@ const styles = StyleSheet.create({
}
})
export default Card
export default React.memo(Card, () => true)

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { Feather } from '@expo/vector-icons'
import Toast from 'react-native-toast-message'
@ -12,6 +12,9 @@ import { getLocalAccountId, getLocalUrl } from 'src/utils/slices/instancesSlice'
import { store } from 'src/store'
import { useTheme } from 'src/utils/styles/ThemeManager'
import constants from 'src/utils/styles/constants'
import BottomSheet from 'src/components/BottomSheet'
import BottomSheetRow from 'src/components/BottomSheet/Row'
import { toast } from 'src/components/toast'
const fireMutation = async ({
id,
@ -32,24 +35,11 @@ const fireMutation = async ({
endpoint: `accounts/${id}/${type}`
})
if (res.body[stateKey] === true) {
Toast.show({
type: 'success',
position: 'bottom',
text1: '功能成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
if (res.body[stateKey!] === true) {
toast({ type: 'success', content: '功能成功' })
return Promise.resolve()
} else {
Toast.show({
type: 'error',
position: 'bottom',
text1: '请重试',
autoHide: false,
bottomOffset: 65
})
toast({ type: 'error', content: '功能错误', autoHide: false })
return Promise.reject()
}
break
@ -64,57 +54,40 @@ const fireMutation = async ({
})
if (!res.body.error) {
Toast.show({
type: 'success',
position: 'bottom',
text1: '隐藏域名成功',
visibilityTime: 2000,
autoHide: true,
bottomOffset: 65
})
toast({ type: 'success', content: '隐藏域名成功' })
return Promise.resolve()
} else {
Toast.show({
toast({
type: 'error',
position: 'bottom',
text1: '隐藏域名失败,请重试',
autoHide: false,
bottomOffset: 65
content: '隐藏域名失败,请重试',
autoHide: false
})
return Promise.reject()
}
break
case 'reports':
console.log('reporting')
res = await client({
method: 'post',
instance: 'local',
endpoint: `reports`,
query: {
account_id: id || ''
}
})
console.log(res.body)
if (!res.body.error) {
toast({ type: 'success', content: '举报账户成功' })
return Promise.resolve()
} else {
toast({
type: 'error',
content: '举报账户失败,请重试',
autoHide: false
})
return Promise.reject()
}
break
// case 'reports':
// res = await client({
// method: 'post',
// instance: 'local',
// endpoint: `reports`,
// query: {
// domain: id || ''
// }
// })
// if (!res.body.error) {
// Toast.show({
// type: 'success',
// position: 'bottom',
// text1: '隐藏域名成功',
// visibilityTime: 2000,
// autoHide: true,
// bottomOffset: 65
// })
// return Promise.resolve()
// } else {
// Toast.show({
// type: 'error',
// position: 'bottom',
// text1: '隐藏域名失败,请重试',
// autoHide: false,
// bottomOffset: 65
// })
// return Promise.reject()
// }
// break
}
}
@ -151,31 +124,12 @@ const HeaderDefault: React.FC<Props> = ({
const [mutateAction] = useMutation(fireMutation, {
onMutate: () => {
queryCache.cancelQueries(queryKey)
const prevData = queryCache.getQueryData(queryKey)
return prevData
const oldData = queryCache.getQueryData(queryKey)
return oldData
},
onSuccess: (newData, params) => {
if (params.type === 'domain_blocks') {
console.log('clearing cache')
queryCache.invalidateQueries(['Following', { page: 'Following' }])
}
// queryCache.setQueryData(queryKey, (oldData: any) => {
// oldData &&
// oldData.map((paging: any) => {
// paging.toots.map(
// (status: Mastodon.Status | Mastodon.Notification, i: number) => {
// if (status.id === newData.id) {
// paging.toots[i] = newData
// }
// }
// )
// })
// return oldData
// })
return Promise.resolve()
},
onError: (err, variables, prevData) => {
queryCache.setQueryData(queryKey, prevData)
onError: (err, _, oldData) => {
toast({ type: 'error', content: '请重试', autoHide: false })
queryCache.setQueryData(queryKey, oldData)
},
onSettled: () => {
queryCache.invalidateQueries(queryKey)
@ -189,6 +143,24 @@ const HeaderDefault: React.FC<Props> = ({
}, 1000)
}, [since])
const onPressAction = useCallback(() => setModalVisible(true), [])
const onPressApplication = useCallback(() => {
navigation.navigate('Webview', {
uri: application!.website
})
}, [])
const pressableAction = useMemo(
() => (
<Feather
name='more-horizontal'
color={theme.secondary}
size={constants.FONT_SIZE_M + 2}
/>
),
[]
)
return (
<View>
<View style={styles.nameAndAction}>
@ -212,17 +184,12 @@ const HeaderDefault: React.FC<Props> = ({
@{account}
</Text>
</View>
{accountId !== localAccountId && domain !== localDomain && (
{(accountId !== localAccountId || domain !== localDomain) && (
<Pressable
style={styles.action}
onPress={() => setModalVisible(true)}
>
<Feather
name='more-horizontal'
color={theme.secondary}
size={constants.FONT_SIZE_M + 2}
/>
</Pressable>
onPress={onPressAction}
children={pressableAction}
/>
)}
</View>
<View style={styles.meta}>
@ -234,11 +201,7 @@ const HeaderDefault: React.FC<Props> = ({
{application && application.name !== 'Web' && (
<View>
<Text
onPress={() => {
navigation.navigate('Webview', {
uri: application.website
})
}}
onPress={onPressApplication}
style={[styles.application, { color: theme.secondary }]}
>
- {application.name}
@ -246,75 +209,65 @@ const HeaderDefault: React.FC<Props> = ({
</View>
)}
</View>
<Modal
animationType='fade'
presentationStyle='overFullScreen'
transparent
<BottomSheet
visible={modalVisible}
handleDismiss={() => setModalVisible(false)}
>
<Pressable
style={styles.modalBackground}
onPress={() => setModalVisible(false)}
>
<View style={styles.modalSheet}>
{accountId !== localAccountId && (
<Pressable
onPress={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'mute',
stateKey: 'muting'
})
}}
>
<Text></Text>
</Pressable>
)}
{accountId !== localAccountId && (
<Pressable
onPress={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'block',
stateKey: 'blocking'
})
}}
>
<Text></Text>
</Pressable>
)}
{domain !== localDomain && (
<Pressable
onPress={() => {
setModalVisible(false)
mutateAction({
id: domain,
type: 'domain_blocks'
})
}}
>
<Text></Text>
</Pressable>
)}
{accountId !== localAccountId && (
<Pressable
onPress={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'reports'
})
}}
>
<Text></Text>
</Pressable>
)}
</View>
</Pressable>
</Modal>
{accountId !== localAccountId && (
<BottomSheetRow
onPressFunction={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'mute',
stateKey: 'muting'
})
}}
icon='eye-off'
text={`隐藏 @${account} 的嘟嘟`}
/>
)}
{accountId !== localAccountId && (
<BottomSheetRow
onPressFunction={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'block',
stateKey: 'blocking'
})
}}
icon='x-circle'
text={`屏蔽用户 @${account}`}
/>
)}
{domain !== localDomain && (
<BottomSheetRow
onPressFunction={() => {
setModalVisible(false)
mutateAction({
id: domain,
type: 'domain_blocks'
})
}}
icon='cloud-off'
text={`屏蔽域名 ${domain}`}
/>
)}
{accountId !== localAccountId && (
<BottomSheetRow
onPressFunction={() => {
setModalVisible(false)
mutateAction({
id: accountId,
type: 'reports'
})
}}
icon='alert-triangle'
text={`举报 @${account}`}
/>
)}
</BottomSheet>
</View>
)
}
@ -349,22 +302,7 @@ const styles = StyleSheet.create({
application: {
fontSize: constants.FONT_SIZE_S,
marginLeft: constants.SPACING_S
},
modalBackground: {
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.75)',
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'flex-end'
},
modalSheet: {
width: '100%',
height: '50%',
backgroundColor: 'white',
flex: 1
}
})
export default HeaderDefault
export default React.memo(HeaderDefault, () => true)

102
src/components/toast.tsx Normal file
View File

@ -0,0 +1,102 @@
import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import Toast from 'react-native-toast-message'
import { useTheme } from 'src/utils/styles/ThemeManager'
import constants from 'src/utils/styles/constants'
import { Feather } from '@expo/vector-icons'
export interface Params {
type: 'success' | 'error' | 'warning'
position?: 'top' | 'bottom'
content: string
description?: string
autoHide?: boolean
onShow?: () => void
onHide?: () => void
}
type Config = {
type: Params['type']
position: Params['position']
text1: Params['content']
text2: Params['description']
}
const toast = ({
type,
position = 'top',
content,
description,
autoHide = true,
onShow,
onHide
}: Params) => {
Toast.show({
type: type,
position: position,
text1: content,
text2: description,
visibilityTime: 2000,
autoHide: autoHide,
topOffset: 0,
bottomOffset: 0,
onShow: onShow,
onHide: onHide
})
}
const ToastBase = ({ config }: { config: Config }) => {
const { theme } = useTheme()
const iconSet = {
success: 'check-circle',
error: 'x-circle',
warning: 'alert-circle'
}
return (
<SafeAreaView
style={[
styles.base,
{ backgroundColor: theme.background, shadowColor: theme.primary }
]}
>
<View style={styles.container}>
<Feather
name={iconSet[config.type]}
color={theme[config.type]}
size={constants.FONT_SIZE_M + 2}
/>
<Text style={[styles.text, { color: theme.primary }]}>
{config.text1}
</Text>
</View>
</SafeAreaView>
)
}
const toastConfig = {
success: (config: Config) => <ToastBase config={config} />,
error: (config: Config) => <ToastBase config={config} />,
warning: (config: Config) => <ToastBase config={config} />
}
const styles = StyleSheet.create({
base: {
width: '100%',
shadowOpacity: 1,
shadowRadius: 6
},
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
padding: constants.SPACING_M
},
text: {
fontSize: constants.FONT_SIZE_M,
marginLeft: constants.SPACING_S
}
})
export { toast, toastConfig }

View File

@ -1,5 +1,6 @@
import React from 'react'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { useSelector } from 'react-redux'
import ScreenMeRoot from 'src/screens/Me/Root'
import ScreenMeConversations from './Me/Cconversations'
@ -8,7 +9,8 @@ import ScreenMeFavourites from './Me/Favourites'
import ScreenMeLists from './Me/Lists'
import sharedScreens from 'src/screens/Shared/sharedScreens'
import ScreenMeListsList from './Me/Root/Lists/List'
import { useSelector } from 'react-redux'
import ScreenMeSettings from './Me/Settings'
import { RootState } from 'src/store'
const Stack = createNativeStackNavigator()
@ -37,7 +39,7 @@ const ScreenMe: React.FC = () => {
name='Screen-Me-Conversations'
component={ScreenMeConversations}
options={{
headerTitle: '对话'
headerTitle: '私信'
}}
/>
<Stack.Screen
@ -51,21 +53,28 @@ const ScreenMe: React.FC = () => {
name='Screen-Me-Favourites'
component={ScreenMeFavourites}
options={{
headerTitle: '书签'
headerTitle: '喜欢'
}}
/>
<Stack.Screen
name='Screen-Me-Lists'
component={ScreenMeLists}
options={{
headerTitle: '书签'
headerTitle: '列表'
}}
/>
<Stack.Screen
name='Screen-Me-Lists-List'
component={ScreenMeListsList}
options={({ route }: any) => ({
headerTitle: `列表:${route.params.title}`
})}
/>
<Stack.Screen
name='Screen-Me-Settings'
component={ScreenMeSettings}
options={{
headerTitle: '书签'
headerTitle: '设置'
}}
/>

View File

@ -1,7 +1,7 @@
import React from 'react'
import { ActivityIndicator, Text } from 'react-native'
import { useQuery } from 'react-query'
import { MenuContainer, MenuHeader, MenuItem } from 'src/components/Menu'
import { MenuContainer, MenuItem } from 'src/components/Menu'
import { listsFetch } from 'src/utils/fetches/listsFetch'
@ -17,25 +17,22 @@ const ScreenMeLists: React.FC = () => {
lists = <Text></Text>
break
case 'success':
lists = data?.map(d => (
lists = data?.map((d: Mastodon.List, i: number) => (
<MenuItem
key={i}
icon='list'
title={d.title}
navigateTo='Screen-Me-Lists-List'
navigateToParams={{
list: d.id
list: d.id,
title: d.title
}}
/>
))
break
}
return (
<MenuContainer>
<MenuHeader heading='我的列表' />
{lists}
</MenuContainer>
)
return <MenuContainer>{lists}</MenuContainer>
}
export default ScreenMeLists

View File

@ -4,7 +4,7 @@ import { MenuContainer, MenuItem } from 'src/components/Menu'
const Settings: React.FC = () => {
return (
<MenuContainer>
<MenuItem icon='settings' title='设置' navigateTo='Local' />
<MenuItem icon='settings' title='设置' navigateTo='Screen-Me-Settings' />
</MenuContainer>
)
}

View File

@ -0,0 +1,9 @@
import React from 'react'
import { MenuContainer } from 'src/components/Menu'
const ScreenMeSettings: React.FC = () => {
return <MenuContainer></MenuContainer>
}
export default ScreenMeSettings

View File

@ -9,7 +9,6 @@ export interface Props {
const limitRatio = 0.4
const AccountHeader: React.FC<Props> = ({ uri, limitHeight = false }) => {
console.log(uri)
useEffect(() => {
if (uri) {
if (uri.includes('/headers/original/missing.png')) {

View File

@ -303,6 +303,5 @@ const Compose: React.FC = () => {
</KeyboardAvoidingView>
)
}
// ;(PostMain as any).whyDidYouRender = true
export default Compose

View File

@ -304,10 +304,6 @@ const PostMain: React.FC<Props> = ({ postState, postDispatch }) => {
)
}
// (PostSuggestions as any).whyDidYouRender = true,
// (PostPoll as any).whyDidYouRender = true,
// (PostEmojis as any).whyDidYouRender = true
const styles = StyleSheet.create({
base: {
flex: 1

View File

@ -12,6 +12,7 @@ export default {
SPACING_XL: 40,
GLOBAL_PAGE_PADDING: 16, // SPACING_M
GLOBAL_SPACING_BASE: 8, // SPACING_S
AVATAR_S: 52,
AVATAR_L: 104

View File

@ -3,11 +3,14 @@ import { DefaultTheme, DarkTheme } from '@react-navigation/native'
export type ColorDefinitions =
| 'primary'
| 'secondary'
| 'disabled'
| 'background'
| 'link'
| 'border'
| 'separator'
| 'dangerous'
| 'success'
| 'error'
| 'warning'
const themeColors: {
[key in ColorDefinitions]: {
@ -23,6 +26,10 @@ const themeColors: {
light: 'rgb(153, 153, 153)',
dark: 'rgb(117, 117, 117)'
},
disabled: {
light: 'rgb(229, 229, 234)',
dark: 'rgb(44, 44, 46)'
},
background: {
light: 'rgb(255, 255, 255)',
dark: 'rgb(0, 0, 0)'
@ -39,9 +46,17 @@ const themeColors: {
light: 'rgba(0, 0, 0, 0.1)',
dark: 'rgba(255, 255, 255, 0.1)'
},
dangerous: {
success: {
light: 'rgb(52, 199, 89)',
dark: 'rgb(48, 209, 88)'
},
error: {
light: 'rgb(255, 59, 48)',
dark: 'rgb(255, 69, 58)'
},
warning: {
light: 'rgb(255, 149, 0)',
dark: 'rgb(255, 159, 10)'
}
}

788
yarn.lock

File diff suppressed because it is too large Load Diff